Long-running ssh commands in python paramiko module (and how to end them)

后端 未结 6 1772
野性不改
野性不改 2020-11-30 02:39

I want to run a tail -f logfile command on a remote machine using python\'s paramiko module. I\'ve been attempting it so far in the following fashion:

6条回答
  •  伪装坚强ぢ
    2020-11-30 03:09

    The way I've solved this is with a context manager. This will make sure my long running commands are aborted. The key logic is to wrap to mimic SSHClient.exec_command but capture the created channel and use a Timer that will close that channel if the command runs for too long.

    import paramiko
    import threading
    
    
    class TimeoutChannel:
    
        def __init__(self, client: paramiko.SSHClient, timeout):
            self.expired = False
            self._channel: paramiko.channel = None
            self.client = client
            self.timeout = timeout
    
        def __enter__(self):
            self.timer = threading.Timer(self.timeout, self.kill_client)
            self.timer.start()
    
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("Exited Timeout. Timed out:", self.expired)
            self.timer.cancel()
    
            if exc_val:
                return False  # Make sure the exceptions are re-raised
    
            if self.expired:
                raise TimeoutError("Command timed out")
    
        def kill_client(self):
            self.expired = True
            print("Should kill client")
            if self._channel:
                print("We have a channel")
                self._channel.close()
    
        def exec(self, command, bufsize=-1, timeout=None, get_pty=False, environment=None):
            self._channel = self.client.get_transport().open_session(timeout=timeout)
            if get_pty:
                self._channel.get_pty()
            self._channel.settimeout(timeout)
            if environment:
                self._channel.update_environment(environment)
            self._channel.exec_command(command)
            stdin = self._channel.makefile_stdin("wb", bufsize)
            stdout = self._channel.makefile("r", bufsize)
            stderr = self._channel.makefile_stderr("r", bufsize)
            return stdin, stdout, stderr
    

    To use the code it's pretty simple now, the first example will throw a TimeoutError

    ssh = paramiko.SSHClient()
    ssh.connect('hostname', username='user', password='pass')
    
    with TimeoutChannel(ssh, 3) as c:
        ssh_stdin, ssh_stdout, ssh_stderr = c.exec("cat")    # non-blocking
        exit_status = ssh_stdout.channel.recv_exit_status()  # block til done, will never complete because cat wants input
    

    This code will work fine (unless the host is under insane load!)

    ssh = paramiko.SSHClient()
    ssh.connect('hostname', username='user', password='pass')
    
    with TimeoutChannel(ssh, 3) as c:
        ssh_stdin, ssh_stdout, ssh_stderr = c.exec("uptime")    # non-blocking
        exit_status = ssh_stdout.channel.recv_exit_status()     # block til done, will complete quickly
        print(ssh_stdout.read().decode("utf8"))                 # Show results
    

提交回复
热议问题