如何知道paramiko SSH通道是否断开连接?

时间:2013-11-22 14:59:24

标签: python paramiko

我正在考虑使用paramiko进行SSH连接的测试用例。测试用例通常包含paramiko.exec_command()个调用,我有一个包装器(称为run_command())。此处self.sshparamiko.SSHClient()的内容。我在每次调用之前使用装饰器来检查ssh连接。 (self.get_ssh()协商连接)

def check_connections(function):
    ''' A decorator to check SSH connections. '''
    def deco(self, *args, **kwargs):
        if self.ssh is None:
            self.ssh = self.get_ssh()
        else:
            ret = getattr(self.ssh.get_transport(), 'is_active', None)
            if ret is None or (ret is not None and not ret()):
                self.ssh = self.get_ssh()
        return function(self, *args, **kwargs)
    return deco
@check_connections
def run_command(self, command):
    ''' Executes command via SSH. '''
    stdin, stdout, stderr = self.ssh.exec_command(command)
    stdin.flush()
    stdin.channel.shutdown_write()
    ret = stdout.read()
    err = stderr.read()
    if ret:
        return ret
    elif err:
        return err
    else:
        return None

它可以完美地工作,直到我的远程节点重新启动,这有时会发生。当它发生时,下一个run_command()调用会生成socket.error个异常。问题是,paramiko.Transport对象似乎保持活动状态,直到抛出异常:

Python 2.7.3 (default, Mar  7 2013, 14:03:36)
[GCC 4.3.4 [gcc-4_3-branch revision 152973]] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> import paramiko
>>> ssh = paramiko.SSHClient()
>>> print ssh
<paramiko.SSHClient object at 0x7f2397b96d50>
>>> print ssh.get_transport()
None
>>> ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> ssh.load_host_keys(os.path.expanduser('~') + '/.ssh/known_hosts')
>>> ssh.connect(hostname = '172.31.77.57', username = 'root', password = 'rootroot', timeout = 5.0)
>>> print ssh
<paramiko.SSHClient object at 0x7f2397b96d50>
>>> print ssh.get_transport()
<paramiko.Transport at 0x97537550L (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>
>>> print ssh.get_transport().is_active()
True
>>> ssh.exec_command('ls')
(<paramiko.ChannelFile from <paramiko.Channel 1 (open) window=2097152 -> <paramiko.Transport at 0x97537550L (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>, <paramiko.ChannelFile from <paramiko.Channel 1 (open) window=2097152 -> <paramiko.Transport at 0x97537550L (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>, <paramiko.ChannelFile from <paramiko.Channel 1 (open) window=2097152 -> <paramiko.Transport at 0x97537550L (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>)
>>> print ssh
<paramiko.SSHClient object at 0x7f2397b96d50>
>>> print ssh.get_transport()
<paramiko.Transport at 0x97537550L (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>
>>> print ssh.get_transport().is_active()
True
>>> ssh.exec_command('reboot')
(<paramiko.ChannelFile from <paramiko.Channel 2 (open) window=2097152 -> <paramiko.Transport at 0x97537550L (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>, <paramiko.ChannelFile from <paramiko.Channel 2 (open) window=2097152 -> <paramiko.Transport at 0x97537550L (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>, <paramiko.ChannelFile from <paramiko.Channel 2 (open) window=2097152 -> <paramiko.Transport at 0x97537550L (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>)
>>> print ssh
<paramiko.SSHClient object at 0x7f2397b96d50>
>>> print ssh.get_transport()
<paramiko.Transport at 0x97537550L (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>
>>> print ssh.get_transport().is_active()
True
>>> ssh.exec_command('ls')
No handlers could be found for logger "paramiko.transport"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/pytest/lib/python2.7/site-packages/paramiko/client.py", line 370, in exec_command
    chan = self._transport.open_session()
  File "/home/pytest/lib/python2.7/site-packages/paramiko/transport.py", line 662, in open_session
    return self.open_channel('session')
  File "/home/pytest/lib/python2.7/site-packages/paramiko/transport.py", line 764, in open_channel
    raise e
socket.error: [Errno 104] Connection reset by peer
>>> print ssh
<paramiko.SSHClient object at 0x7f2397b96d50>
>>> print ssh.get_transport()
<paramiko.Transport at 0x97537550L (unconnected)>
>>> print ssh.get_transport().is_active()
False
>>>

问题:如何确定连接是否真正有效?

4 个答案:

答案 0 :(得分:10)

在python中,它是easier to ask for forgiveness than permission

将每次通话都包含在ssh.exec_command中,如下所示:

try:
    ssh.exec_command('ls')
except socket.error as e:
    # Crap, it's closed. Perhaps reopen and retry?

答案 1 :(得分:2)

我的解决方案与您的解决方案基本相同,只是采用不同的方式组织:

def connection(self):
    if not self.is_connected():
        self._ssh = paramiko.SSHClient()
        self._ssh.connect(self.server, self.port,
                          username = self.username, password = self.password)

    return self._ssh

def is_connected(self):
    transport = self._ssh.get_transport() if self._ssh else None
    return transport and transport.is_active()

def do_something(self):
    self.connection().exec_command('ls')

答案 2 :(得分:1)

这有效:

import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())        # Setting the missing host policy to auto add it
client.connect('192.168.1.16', port=22, username='admin', password='admin', timeout=3, banner_timeout=2)

channel = client.invoke_shell()                 # Request an interactive shell session on this channel. If the server allows it, the channel will then be directly connected to the stdin, stdout, and stderr of the shell.
print channel.closed          # False
command = 'reboot'
channel.send(command + '\n')
# wait a while
print channel.closed          # True

答案 3 :(得分:0)

我会把它扔在这里,因为有人可能会觉得有用。其中一些方法有一个陷阱。 Paramiko内部使用sockets。每个新连接都会调用socket,这将打开一个新的文件描述符。由于一段时间后进程仅限于一定数量的打开文件描述符,因此您将用光,这将导致:

socket.error: [Errno 24] Too many open files

因此,最好在使用SSHClient.close()方法建立新连接之前先明确尝试关闭连接。