2012-11-09 36 views
5

Có rất nhiều chủ đề được chạm vào một phần của tiêu đề, nhưng không có gì hoàn toàn thỏa mãn toàn bộ. Tôi đang đẩy một lệnh trên một máy chủ từ xa và cần đầu ra đầy đủ sau một thời gian thực hiện dài, nói rằng 5 phút hoặc lâu hơn. Sử dụng kênh tôi đã có thể thiết lập một thời gian chờ, nhưng khi tôi đọc trở lại stdout tôi chỉ nhận được một phần nhỏ của đầu ra. Giải pháp dường như chờ channel.exit_status_ready(). Điều này làm việc trên một cuộc gọi thành công, nhưng một cuộc gọi không thành công sẽ không bao giờ kích hoạt thời gian chờ của kênh. Sau khi xem xét các tài liệu, tôi giả định đó là vì thời gian chờ chỉ hoạt động trên một hoạt động đọc và chờ trạng thái thoát không đủ điều kiện. Đây là nỗ lực đó:Thời gian chờ của Python Paramiko với thời gian thực thi dài, cần có đầu ra đầy đủ

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 

Khá, phải không? Muốn nó hoạt động.

Nỗ lực đầu tiên của tôi tại giải pháp là sử dụng time.time() để bắt đầu, sau đó kiểm tra start - time.time()> timeout. Điều này có vẻ đơn giản, nhưng trong phiên bản hiện tại của tôi, tôi xuất đầu - time.time() với thời gian chờ cố định sẽ kích hoạt ngắt ... và thấy sự khác biệt gấp đôi và gấp ba lần thời gian chờ mà không xảy ra sự cố. Để tiết kiệm không gian, tôi sẽ đề cập đến nỗ lực thứ ba của tôi, mà tôi đã cuộn lên với cái này. Tôi đọc ở đây về việc sử dụng select.select để chờ đầu ra, và lưu ý trong tài liệu có thời gian chờ ở đó. Như bạn sẽ thấy từ mã bên dưới, tôi đã trộn cả ba phương thức - thời gian chờ của kênh, thời gian chờ time.time và chọn thời gian chờ - nhưng vẫn phải giết quá trình. Đây là 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 

Dưới đây là một số kết quả tiêu biểu:

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

Dòng đầu là [rlist, wlist, elist] từ chọn, điểm mấu chốt là time.time() - start = (time.time() - bắt đầu). Tôi đã chạy này để phá vỡ bằng cách đếm các lần lặp và phá vỡ ở dưới cùng của thử sau khi lặp 1000 lần. thời gian chờ được đặt thành 3 khi chạy mẫu. Điều đó chứng minh rằng chúng tôi vượt qua được thử thách, nhưng rõ ràng, không có cách nào trong ba cách đó là thời gian hoạt động.

Hãy thoải mái trích xuất mã nếu tôi đã hiểu sai về cơ bản điều gì đó. Tôi muốn cho điều này là uber-Pythonic và vẫn đang học.

Trả lời

2

Tôi đang gặp cùng một loại vấn đề. Tôi nghĩ chúng ta có thể xử lý nó bằng tín hiệu. http://docs.python.org/2/library/signal.html

Đây là ví dụ đơn giản để hiển thị cách hoạt động.

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)        

Ở đây, báo thức được đặt thành 2 giây. Time.sleep được gọi với 10 giây. Tất nhiên, báo động sẽ được kích hoạt trước khi giấc ngủ kết thúc. Nếu bạn đặt một số đầu ra sau khi time.sleep, bạn sẽ thấy rằng thực thi chương trình tiếp tục ở đó.

Nếu bạn muốn điều khiển tiếp tục ở nơi khác, hãy kết thúc cuộc gọi treo của bạn trong một lần thử/trừ và có chức năng xử lý của bạn tăng ngoại lệ.

Mặc dù tôi khá chắc chắn rằng nó sẽ hoạt động, tôi chưa thử nghiệm nó trên các cuộc gọi paramiko.

+0

Nghiên cứu của tôi chạy theo cùng một hướng, nhưng tôi nhận được "ValueError: tín hiệu chỉ hoạt động trong chuỗi chính", mặc dù tôi không cố ý sử dụng các chuỗi trong mã của mình. Hoặc là một số mô-đun đang thúc đẩy quá trình hoặc đây là lỗi. Suy nghĩ? – user1772459

+0

Vâng, tôi nhận ra rằng python chỉ hỗ trợ tín hiệu trong chuỗi chính. Nếu bạn nhận được thông điệp đó, thì tôi đoán có gì đó sinh ra các chủ đề tại một thời điểm nào đó. –

2

Đây là điều có thể hữu ích, mặc dù tôi vẫn đang trong giai đoạn thử nghiệm.Sau khi vật lộn với timeouts các loại khác nhau bao gồm một nhận tất cả thời gian chờ cho Python, và nhận ra rằng vấn đề thực sự là máy chủ không thể được tin cậy để chấm dứt quá trình này, tôi đã làm điều này:

chan = ssh.get_transport().open_session() 

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

chan.exec_command(cmd) 

Giờ máy chủ ra sau timeouttime nếu cmd không thoát sớm hơn, chính xác như tôi muốn và lệnh đã kết thúc sẽ giết kênh. Điểm duy nhất là các lõi của GNU phải tồn tại trên máy chủ. Không có lựa chọn thay thế.

+0

Điều gì làm việc cho tôi là một biến thể ở trên: 'timeout -s SIGKILL ', nếu không chương trình không bị giết. – Lidia

0

Tôi đã có rất nhiều vấn đề gọi exec_command từ các kênh, thay vào đó tôi sử dụng trực tiếp các exec_command từ kết nối ssh và gọi cho kênh đầu ra tiêu chuẩn, mã mà làm việc đối với tôi là như 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 

Tôi sử dụng Debian 8 và Python 2.7.13, chúc bạn may mắn.

Các vấn đề liên quan