web-dev-qa-db-ja.com

paramiko SSHチャネルが切断されているかどうかを知る方法は?

SSH接続にparamikoを使用するテストケースを設計しています。テストケースには通常、ラッパーを持っているparamiko.exec_command()呼び出しが含まれています(run_command()と呼ばれます)。ここで、_self.ssh_はparamiko.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
>>>
_

質問:接続が本当にアクティブであるかどうかを確認するにはどうすればよいですか?

11
Milo

Pythonでは 許可よりも許しを求める方が簡単です

ssh.exec_commandへの各呼び出しを次のようにラップします。

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

私のソリューションは基本的にあなたのソリューションと同じですが、編成が異なります。

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
Haroldo_OK

誰かがそれを役に立つと思うかもしれないので、私はこれをここに投げます。これらの方法のいくつかには1つの落とし穴があります。 Paramikoは内部的にsocketsを使用します。新しい接続はすべてsocketを呼び出し、新しいファイル記述子を開きます。プロセスは一定数の開いているファイル記述子に制限されているため、しばらくすると不足し、次のような結果になります。

_socket.error: [Errno 24] Too many open files_。

したがって、SSHClient.close()メソッドを使用して新しい接続を確立する前に、明示的に接続を閉じようとすることをお勧めします。

1
Majo

これは機能します:

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
1
Ashutosh Kumar