Skip to content

Commit f1c149f

Browse files
authored
Added debug logging function, run_command timeout updates (#244)
* Added debug logging function * Updated tests, added timeout test * Updated reader * Updated CI cfg * Updated change log
1 parent 801b76a commit f1c149f

File tree

12 files changed

+89
-18
lines changed

12 files changed

+89
-18
lines changed

.circleci/config.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ jobs:
4545
name: Integration tests
4646
- run:
4747
command: |
48-
cd doc; make html; cd ..
48+
cd doc; make html
49+
cd ..
4950
name: make docs
5051
- run:
5152
command: |
5253
python setup.py sdist
53-
cd dist; pip install *; cd ..
54+
cd dist; pip install *
55+
cd ..
5456
name: Source dist install
5557
- run:
5658
command: codecov

Changelog.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
Change Log
22
============
33

4+
2.4.0
5+
+++++
6+
7+
Changes
8+
-------
9+
10+
* Added ``pssh.utils.enable_debug_logger`` function.
11+
* ``ParallelSSHClient`` timeout parameter is now also applied to starting remote commands.
12+
13+
Fixes
14+
-----
15+
16+
* ``SSHClient`` with proxy enabled could not be used without setting port - #
17+
18+
419
2.3.2
520
+++++
621

pssh/clients/base/single.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,9 @@ def run_command(self, command, sudo=False, user=None,
342342
_command = 'sudo -u %s -S ' % (user,)
343343
_shell = shell if shell else '$SHELL -c'
344344
_command += "%s '%s'" % (_shell, command,)
345+
with GTimeout(seconds=self.timeout):
346+
channel = self.execute(_command, use_pty=use_pty)
345347
_timeout = read_timeout if read_timeout else timeout
346-
channel = self.execute(_command, use_pty=use_pty)
347348
_stdout_buffer = ConcurrentRWBuffer()
348349
_stderr_buffer = ConcurrentRWBuffer()
349350
_stdout_reader, _stderr_reader = self._make_output_readers(

pssh/clients/native/single.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,9 @@ def _password_auth(self):
252252
def open_session(self):
253253
"""Open new channel from session"""
254254
try:
255-
chan = self.session.open_session()
255+
chan = self._eagain(self.session.open_session)
256256
except Exception as ex:
257257
raise SessionError(ex)
258-
while chan == LIBSSH2_ERROR_EAGAIN:
259-
self.poll()
260-
try:
261-
chan = self.session.open_session()
262-
except Exception as ex:
263-
raise SessionError(ex)
264258
# Multiple forward requests result in ChannelRequestDenied
265259
# errors, flag is used to avoid this.
266260
if self.forward_ssh_agent and not self._forward_requested:
@@ -727,6 +721,7 @@ def poll(self, timeout=None):
727721
Blocks current greenlet only if socket has pending read or write operations
728722
in the appropriate direction.
729723
"""
724+
timeout = self.timeout if timeout is None else timeout
730725
directions = self.session.block_directions()
731726
if directions == 0:
732727
return

pssh/clients/reader.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@
2828
class ConcurrentRWBuffer(object):
2929
"""Concurrent reader/writer of bytes for use from multiple greenlets.
3030
31-
Iterate on buffer object to read data, blocking greenlet if no data exists
31+
Iterate on buffer object to read data, yielding greenlet if no data exists
3232
until self.eof has been set.
3333
3434
Writers should ``eof.set()`` when finished writing data via ``write``.
3535
Readers can use ``read()`` to get any available data, or None.
3636
"""
37+
__slots__ = ('_buffer', '_read_pos', '_write_pos', 'eof', '_lock')
3738

3839
def __init__(self):
3940
self._buffer = BytesIO()
@@ -54,7 +55,10 @@ def write(self, data):
5455
self._write_pos += self._buffer.write(data)
5556

5657
def read(self):
57-
"""Read available data, or return None"""
58+
"""Read available data, or return None
59+
60+
:rtype: bytes
61+
"""
5862
with self._lock:
5963
if self._write_pos == 0 or self._read_pos == self._write_pos:
6064
return

pssh/clients/ssh/single.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def open_session(self):
216216
while channel.open_session() == SSH_AGAIN:
217217
logger.debug(
218218
"Channel open session blocked, waiting on socket..")
219-
self.poll(timeout=self.timeout)
219+
self.poll()
220220
# Select on open session can dead lock without
221221
# yielding event loop
222222
sleep(.1)
@@ -323,6 +323,7 @@ def close_channel(self, channel):
323323

324324
def poll(self, timeout=None):
325325
"""ssh-python based co-operative gevent select on session socket."""
326+
timeout = self.timeout if timeout is None else timeout
326327
directions = self.session.get_poll_flags()
327328
if directions == 0:
328329
return

pssh/utils.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
def enable_logger(_logger, level=logging.INFO):
2828
"""Enables logging to stdout for given logger"""
29+
_logger.setLevel(level)
2930
stream_handlers = [h for h in _logger.handlers
3031
if isinstance(h, logging.StreamHandler)]
3132
if stream_handlers:
@@ -35,12 +36,15 @@ def enable_logger(_logger, level=logging.INFO):
3536
host_log_format = logging.Formatter('%(message)s')
3637
handler.setFormatter(host_log_format)
3738
_logger.addHandler(handler)
38-
_logger.setLevel(level)
3939

4040

4141
def enable_host_logger():
4242
"""Enable host logger for logging stdout from remote commands
4343
as it becomes available.
44-
4544
"""
4645
enable_logger(host_logger)
46+
47+
48+
def enable_debug_logger():
49+
"""Enable debug logging for the library to sdout."""
50+
return enable_logger(logger, level=logging.DEBUG)

tests/native/test_parallel_client.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,19 @@ def test_pssh_client_timeout(self):
226226
self.assertIsInstance(output[0].exception,
227227
Timeout)
228228

229+
def test_timeout_on_open_session(self):
230+
timeout = 1
231+
client = ParallelSSHClient([self.host], port=self.port,
232+
pkey=self.user_key,
233+
timeout=timeout,
234+
num_retries=1)
235+
def _session(timeout=1):
236+
sleep(timeout+1)
237+
joinall(client.connect_auth())
238+
sleep(.01)
239+
client._host_clients[(0, self.host)].open_session = _session
240+
self.assertRaises(Timeout, client.run_command, self.cmd)
241+
229242
def test_connection_timeout(self):
230243
client_timeout = .01
231244
host = 'fakehost.com'

tests/native/test_single_client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from hashlib import sha256
2424
from datetime import datetime
2525

26-
from gevent import socket, sleep, spawn
26+
from gevent import socket, sleep, spawn, Timeout as GTimeout
2727

2828
from pssh.clients.native import SSHClient
2929
from ssh2.session import Session
@@ -71,6 +71,16 @@ def test_execute(self):
7171
self.assertEqual(host_out.exit_code, 0)
7272
self.assertEqual(expected, output)
7373

74+
def test_open_session_timeout(self):
75+
client = SSHClient(self.host, port=self.port,
76+
pkey=self.user_key,
77+
num_retries=1,
78+
timeout=1)
79+
def _session(timeout=2):
80+
sleep(2)
81+
client.open_session = _session
82+
self.assertRaises(GTimeout, client.run_command, self.cmd)
83+
7484
def test_finished_error(self):
7585
self.assertRaises(ValueError, self.client.wait_finished, None)
7686
self.assertIsNone(self.client.finished(None))

tests/ssh/test_parallel_client.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from datetime import datetime
2222
from sys import version_info
2323

24-
from gevent import joinall, spawn, socket, Greenlet
24+
from gevent import joinall, spawn, socket, Greenlet, sleep
2525
from pssh import logger as pssh_logger
2626
from pssh.output import HostOutput
2727
from pssh.exceptions import UnknownHostException, \
@@ -79,6 +79,19 @@ def make_random_port(self):
7979
sock.close()
8080
return listen_port
8181

82+
def test_timeout_on_open_session(self):
83+
timeout = 1
84+
client = ParallelSSHClient([self.host], port=self.port,
85+
pkey=self.user_key,
86+
timeout=timeout,
87+
num_retries=1)
88+
def _session(timeout=1):
89+
sleep(timeout+1)
90+
joinall(client.connect_auth())
91+
sleep(.01)
92+
client._host_clients[(0, self.host)].open_session = _session
93+
self.assertRaises(Timeout, client.run_command, self.cmd)
94+
8295
def test_join_timeout(self):
8396
client = ParallelSSHClient([self.host], port=self.port,
8497
pkey=self.user_key)

0 commit comments

Comments
 (0)