Skip to content

Commit e290bd7

Browse files
author
Pan
committed
Added keepalive functionality and integration tests.
Updated changelog.
1 parent 9791fff commit e290bd7

File tree

5 files changed

+36
-5
lines changed

5 files changed

+36
-5
lines changed

Changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Changes
99

1010
* Removed libssh2 native library dependency in favour of bundled ssh2-python libssh2 library.
1111
* Changed native client forward agent default behaviour to off due to incompatibility with certain SSH server implementations.
12+
* Added keep-alive functionality to native client - defaults to ``60`` seconds. ``ParallelSSHClient.keepalive_seconds`` to configure interval, ``0`` to disable.
1213

1314

1415
1.8.2

pssh/clients/native/parallel.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ def __init__(self, hosts, user=None, password=None, port=22, pkey=None,
3939
allow_agent=True, host_config=None, retry_delay=RETRY_DELAY,
4040
proxy_host=None, proxy_port=22,
4141
proxy_user=None, proxy_password=None, proxy_pkey=None,
42-
forward_ssh_agent=False, tunnel_timeout=None):
42+
forward_ssh_agent=False, tunnel_timeout=None,
43+
keepalive_seconds=60):
4344
"""
4445
:param hosts: Hosts to connect to
4546
:type hosts: list(str)
@@ -122,6 +123,7 @@ def __init__(self, hosts, user=None, password=None, port=22, pkey=None,
122123
self._tunnel_lock = None
123124
self._tunnel_timeout = tunnel_timeout
124125
self._clients_lock = RLock()
126+
self.keepalive_seconds = keepalive_seconds
125127

126128
def run_command(self, command, sudo=False, user=None, stop_on_errors=True,
127129
use_pty=False, host_args=None, shell=None,
@@ -381,7 +383,8 @@ def _make_ssh_client(self, host):
381383
timeout=self.timeout,
382384
allow_agent=self.allow_agent, retry_delay=self.retry_delay,
383385
proxy_host=proxy_host, _auth_thread_pool=auth_thread_pool,
384-
forward_ssh_agent=self.forward_ssh_agent)
386+
forward_ssh_agent=self.forward_ssh_agent,
387+
keepalive_seconds=self.keepalive_seconds)
385388

386389
def copy_file(self, local_file, remote_file, recurse=False, copy_args=None):
387390
"""Copy local file to remote file in parallel

pssh/clients/native/single.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from socket import gaierror as sock_gaierror, error as sock_error
2727
from warnings import warn
2828

29-
from gevent import sleep, socket, get_hub
29+
from gevent import sleep, socket, get_hub, spawn
3030
from gevent.hub import Hub
3131
from ssh2.error_codes import LIBSSH2_ERROR_EAGAIN
3232
from ssh2.exceptions import SFTPHandleError, SFTPProtocolError, \
@@ -68,7 +68,7 @@ def __init__(self, host,
6868
allow_agent=True, timeout=None,
6969
forward_ssh_agent=False,
7070
proxy_host=None,
71-
_auth_thread_pool=True):
71+
_auth_thread_pool=True, keepalive_seconds=60):
7272
""":param host: Host name or IP to connect to.
7373
:type host: str
7474
:param user: User to connect as. Defaults to logged in user.
@@ -100,6 +100,8 @@ def __init__(self, host,
100100
:param proxy_host: Connection to host is via provided proxy host
101101
and client should use self.proxy_host for connection attempts.
102102
:type proxy_host: str
103+
:param keepalive_seconds: Interval of keep alive messages being sent to
104+
server. Set to ``0`` or ``False`` to disable.
103105
104106
:raises: :py:class:`pssh.exceptions.PKeyFileError` on errors finding
105107
provided private key.
@@ -123,6 +125,8 @@ def __init__(self, host,
123125
self._host = proxy_host if proxy_host else host
124126
self.pkey = _validate_pkey_path(pkey, self.host)
125127
self._connect(self._host, self.port)
128+
self.keepalive_seconds = keepalive_seconds
129+
self._keepalive_greenlet = None
126130
if _auth_thread_pool:
127131
THREAD_POOL.apply(self._init)
128132
else:
@@ -147,6 +151,18 @@ def __enter__(self):
147151
def __exit__(self, *args):
148152
self.disconnect()
149153

154+
def spawn_send_keepalive(self):
155+
"""Spawns a new greenlet that sends keep alive messages every
156+
self.keepalive_seconds"""
157+
return spawn(self._send_keepalive)
158+
159+
def _send_keepalive(self):
160+
while True:
161+
sleep(self._eagain(self.session.keepalive_send))
162+
163+
def configure_keepalive(self):
164+
self.session.keepalive_config(False, self.keepalive_seconds)
165+
150166
def _connect_init_retry(self, retries):
151167
retries += 1
152168
self.session = None
@@ -179,6 +195,9 @@ def _init(self, retries=1):
179195
msg = "Authentication error while connecting to %s:%s - %s"
180196
raise AuthenticationException(msg, self.host, self.port, ex)
181197
self.session.set_blocking(0)
198+
if self.keepalive_seconds:
199+
self.configure_keepalive()
200+
self._keepalive_greenlet = self.spawn_send_keepalive()
182201

183202
def _connect(self, host, port, retries=1):
184203
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

tests/test_native_parallel_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,3 +1401,11 @@ def test_disable_agent_forward(self):
14011401
num_retries=1)
14021402
client.join(client.run_command(self.cmd))
14031403
self.assertFalse(client.host_clients[self.host].forward_ssh_agent)
1404+
1405+
def test_keepalive_off(self):
1406+
client = ParallelSSHClient(
1407+
[self.host], port=self.port, pkey=self.user_key,
1408+
keepalive_seconds=0,
1409+
num_retries=1)
1410+
client.join(client.run_command(self.cmd))
1411+
self.assertFalse(client.host_clients[self.host].keepalive_seconds)

tests/test_native_single_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,4 @@ def test_connection_timeout(self):
155155
num_retries=1, timeout=1, _auth_thread_pool=False)
156156
# Should fail within greenlet timeout, otherwise greenlet will
157157
# raise timeout which will fail the test
158-
self.assertRaises(ConnectionErrorException, cmd.get, timeout=1.1)
158+
self.assertRaises(ConnectionErrorException, cmd.get, timeout=2)

0 commit comments

Comments
 (0)