Skip to content

Commit

Permalink
Client close no join (#206)
Browse files Browse the repository at this point in the history
* Correctly handle client disconnection - state can be left in libssh2 on disconnected sessions. Resolves #200
* Updated Changelog
  • Loading branch information
pkittenis authored Aug 25, 2020
1 parent 47cbb22 commit 4f23edc
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 12 deletions.
9 changes: 9 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Change Log
============

1.11.2
++++++

Fixes
------

* `ParallelSSHClient.disconnect` would cause new client sessions to fail if `client.join` was not called prior - #200


1.11.0
++++++

Expand Down
13 changes: 8 additions & 5 deletions pssh/clients/native/single.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,20 @@ def __init__(self, host,
def disconnect(self):
"""Disconnect session, close socket if needed."""
logger.debug("Disconnecting client for host %s", self.host)
self._keepalive_greenlet = None
if self.session is not None:
try:
self._eagain(self.session.disconnect)
except Exception:
pass
if self.sock is not None and not self.sock.closed:
self.sock.close()
logger.debug("Client socket closed for host %s", self.host)
self.session = None
self.sock = None

def __del__(self):
self.disconnect()
try:
self.disconnect()
except Exception:
pass

def __enter__(self):
return self
Expand Down Expand Up @@ -328,7 +331,7 @@ def execute(self, cmd, use_pty=False, channel=None):
channel = self.open_session() if channel is None else channel
if use_pty:
self._eagain(channel.pty)
logger.debug("Executing command '%s'" % cmd)
logger.debug("Executing command '%s'", cmd)
self._eagain(channel.execute, cmd)
return channel

Expand Down
10 changes: 6 additions & 4 deletions tests/embedded_server/openssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def __init__(self, listen_ip='127.0.0.1', port=2222):
self.listen_ip = listen_ip
self.port = port
self.server_proc = None
self.sshd_config = SSHD_CONFIG + '_%s' % ''.join(
random.choice(string.ascii_lowercase + string.digits)
for _ in range(8))
self.random_server = ''.join(random.choice(string.ascii_lowercase + string.digits)
for _ in range(8))
self.sshd_config = SSHD_CONFIG + '_%s' % self.random_server
self._fix_masks()
self.make_config()

Expand All @@ -60,7 +60,9 @@ def make_config(self):
template = Template(tmpl)
with open(self.sshd_config, 'w') as fh:
fh.write(template.render(parent_dir=os.path.abspath(DIR_NAME),
listen_ip=self.listen_ip))
listen_ip=self.listen_ip,
random_server=self.random_server,
))
fh.write(os.linesep)

def start_server(self):
Expand Down
2 changes: 2 additions & 0 deletions tests/embedded_server/sshd_config.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ ListenAddress {{listen_ip}}
AcceptEnv LANG LC_*
Subsystem sftp internal-sftp
AuthorizedKeysFile {{parent_dir}}/authorized_keys
MaxSessions 100
PidFile {{random_server}}.pid
3 changes: 1 addition & 2 deletions tests/test_native_parallel_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,5 @@ def test_client_disconnect(self):
return_list=True)
client.join(output, consume_output=True)
single_client = list(client._host_clients.values())[0]
self.assertFalse(single_client.sock.closed)
del client
self.assertTrue(single_client.sock.closed)
self.assertEqual(single_client.session, None)
15 changes: 15 additions & 0 deletions tests/test_native_single_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,18 @@ def test_connection_timeout(self):
# Should fail within greenlet timeout, otherwise greenlet will
# raise timeout which will fail the test
self.assertRaises(ConnectionErrorException, cmd.get, timeout=2)

def test_multiple_clients_exec_terminates_channels(self):
# See #200 - Multiple clients should not interfere with
# each other. session.disconnect can leave state in libssh2
# and break subsequent sessions even on different socket and
# session
for _ in range(5):
client = SSHClient(self.host, port=self.port,
pkey=self.user_key,
num_retries=1,
allow_agent=False)
channel = client.execute(self.cmd)
output = list(client.read_output(channel))
self.assertListEqual(output, [b'me'])
client.disconnect()
2 changes: 1 addition & 1 deletion tests/test_native_tunnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def test_tunnel_channel_failure(self):
tunnel.cleanup()
spawn(proxy_client.execute, 'echo me')
proxy_client.disconnect()
self.assertTrue(proxy_client.sock.closed)
self.assertEqual(proxy_client.sock, None)
finally:
remote_server.stop()

Expand Down

0 comments on commit 4f23edc

Please sign in to comment.