Skip to content

Commit 2e7b811

Browse files
author
Dan
committed
Added consume output optional argument to join. Added null handlers to loggers. Resolves #74
1 parent c0d9d22 commit 2e7b811

File tree

3 files changed

+89
-60
lines changed

3 files changed

+89
-60
lines changed

pssh/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,6 @@
3737
AuthenticationException, ConnectionErrorException, SSHException
3838

3939
host_logger = logging.getLogger('pssh.host_logger')
40+
host_logger.addHandler(logging.NullHandler())
4041
logger = logging.getLogger('pssh')
42+
logger.addHandler(logging.NullHandler())

pssh/pssh_client.py

Lines changed: 76 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This file is part of parallel-ssh.
22

3-
# Copyright (C) 2014- Panos Kittenis
3+
# Copyright (C) 2014-2017 Panos Kittenis
44

55
# This library is free software; you can redistribute it and/or
66
# modify it under the terms of the GNU Lesser General Public
@@ -48,77 +48,80 @@
4848
class ParallelSSHClient(object):
4949
"""Uses :py:class:`pssh.ssh_client.SSHClient`, performs tasks over SSH on multiple hosts in \
5050
parallel.
51-
51+
5252
Connections to hosts are established in parallel when ``run_command`` is called,
5353
therefor any connection and/or authentication exceptions will happen on the
5454
call to ``run_command`` and need to be caught.
5555
"""
56-
57-
def __init__(self, hosts,
58-
user=None, password=None, port=None, pkey=None,
59-
forward_ssh_agent=True, num_retries=DEFAULT_RETRIES, timeout=120,
60-
pool_size=10, proxy_host=None, proxy_port=22, proxy_user=None,
61-
proxy_password=None, proxy_pkey=None,
62-
agent=None, allow_agent=True, host_config=None, channel_timeout=None):
56+
57+
def __init__(self, hosts, user=None, password=None, port=None, pkey=None,
58+
forward_ssh_agent=True, num_retries=DEFAULT_RETRIES,
59+
timeout=120, pool_size=10, proxy_host=None, proxy_port=22,
60+
proxy_user=None, proxy_password=None, proxy_pkey=None,
61+
agent=None, allow_agent=True, host_config=None,
62+
channel_timeout=None):
6363
"""
6464
:param hosts: Hosts to connect to
6565
:type hosts: list(str)
66-
:param user: (Optional) User to login as. Defaults to logged in user or \
67-
user from ~/.ssh/config or /etc/ssh/ssh_config if set
66+
:param user: (Optional) User to login as. Defaults to logged in user or
67+
user from ~/.ssh/config or /etc/ssh/ssh_config if set
6868
:type user: str
69-
:param password: (Optional) Password to use for login. Defaults to \
70-
no password
69+
:param password: (Optional) Password to use for login. Defaults to
70+
no password
7171
:type password: str
72-
:param port: (Optional) Port number to use for SSH connection. Defaults \
73-
to None which uses SSH default
72+
:param port: (Optional) Port number to use for SSH connection. Defaults
73+
to ``None`` which uses SSH default
7474
:type port: int
7575
:param pkey: (Optional) Client's private key to be used to connect with
7676
:type pkey: :py:class:`paramiko.pkey.PKey`
77-
:param num_retries: (Optional) Number of retries for connection attempts \
78-
before the client gives up. Defaults to 3.
77+
:param num_retries: (Optional) Number of retries for connection attempts
78+
before the client gives up. Defaults to 3.
7979
:type num_retries: int
80-
:param timeout: (Optional) Number of seconds to timeout connection \
81-
attempts before the client gives up. Defaults to 10.
80+
:param timeout: (Optional) Number of seconds to wait before connection
81+
and authentication attempt times out. Note that total time before
82+
timeout will be
83+
``timeout`` * ``num_retries`` + (5 * (``num_retries``-1)) number of
84+
seconds, where (5 * (``num_retries``-1)) refers to a five (5) second
85+
delay between retries.
8286
:type timeout: int
83-
:param forward_ssh_agent: (Optional) Turn on SSH agent forwarding - \
84-
equivalent to `ssh -A` from the `ssh` command line utility. \
85-
Defaults to True if not set.
87+
:param forward_ssh_agent: (Optional) Turn on SSH agent forwarding -
88+
equivalent to `ssh -A` from the `ssh` command line utility.
89+
Defaults to ``True`` if not set.
8690
:type forward_ssh_agent: bool
87-
:param pool_size: (Optional) Greenlet pool size. Controls on how many\
88-
hosts to execute tasks in parallel. Defaults to 10. Values over 500 \
89-
are not likely to increase performance due to overhead in the single \
90-
thread running our event loop.
91+
:param pool_size: (Optional) Greenlet pool size. Controls on how many
92+
hosts to execute tasks in parallel. Defaults to 10. Overhead in event
93+
loop will determine how high this can be set to, see scaling guide
94+
lines in project's readme.
9195
:type pool_size: int
92-
:param proxy_host: (Optional) SSH host to tunnel connection through \
93-
so that SSH clients connect to self.host via client -> proxy_host -> \
94-
host
96+
:param proxy_host: (Optional) SSH host to tunnel connection through
97+
so that SSH clients connect to host via client -> proxy_host -> host
9598
:type proxy_host: str
96-
:param proxy_port: (Optional) SSH port to use to login to proxy host if \
97-
set. Defaults to 22.
99+
:param proxy_port: (Optional) SSH port to use to login to proxy host if
100+
set. Defaults to 22.
98101
:type proxy_port: int
99-
:param proxy_user: (Optional) User to login to ``proxy_host`` as. Defaults to \
100-
logged in user.
102+
:param proxy_user: (Optional) User to login to ``proxy_host`` as.
103+
Defaults to logged in user.
101104
:type proxy_user: str
102-
:param proxy_password: (Optional) Password to login to ``proxy_host`` with. \
103-
Defaults to no password
105+
:param proxy_password: (Optional) Password to login to ``proxy_host``
106+
with. Defaults to no password
104107
:type proxy_password: str
105-
:param proxy_pkey: (Optional) Private key to be used for authentication \
106-
with ``proxy_host``. Defaults to available keys from SSHAgent and user's \
107-
home directory keys
108+
:param proxy_pkey: (Optional) Private key to be used for authentication
109+
with ``proxy_host``. Defaults to available keys from SSHAgent and user's
110+
home directory keys
108111
:type proxy_pkey: :py:class:`paramiko.pkey.PKey`
109-
:param agent: (Optional) SSH agent object to programmatically supply an \
110-
agent to override system SSH agent with
112+
:param agent: (Optional) SSH agent object to programmatically supply an
113+
agent to override system SSH agent with
111114
:type agent: :py:class:`pssh.agent.SSHAgent`
112-
:param host_config: (Optional) Per-host configuration for cases where \
113-
not all hosts use the same configuration values.
115+
:param host_config: (Optional) Per-host configuration for cases where
116+
not all hosts use the same configuration values.
114117
:type host_config: dict
115-
:param channel_timeout: (Optional) Time in seconds before reading from \
116-
an SSH channel times out. For example with channel timeout set to one, \
117-
trying to immediately gather output from a command producing no output \
118-
for more than one second will timeout.
118+
:param channel_timeout: (Optional) Time in seconds before reading from
119+
an SSH channel times out. For example with channel timeout set to one,
120+
trying to immediately gather output from a command producing no output
121+
for more than one second will timeout.
119122
:type channel_timeout: int
120-
:param allow_agent: (Optional) set to False to disable connecting to \
121-
the system's SSH agent
123+
:param allow_agent: (Optional) set to False to disable connecting to
124+
the system's SSH agent
122125
:type allow_agent: bool
123126
124127
**Example Usage**
@@ -261,8 +264,8 @@ def __init__(self, hosts,
261264
262265
.. code-block:: python
263266
264-
import paramiko
265-
client_key = paramiko.RSAKey.from_private_key_file('user.key')
267+
from pssh.utils import load_private_key
268+
client_key = load_private_key('user.key')
266269
client = ParallelSSHClient(['myhost1', 'myhost2'], pkey=client_key)
267270
268271
**Multiple commands**
@@ -274,13 +277,14 @@ def __init__(self, hosts,
274277
275278
**Per-Host configuration**
276279
277-
Per host configuration can be provided for any or all of user, password port
278-
and private key. Private key value is a :py:class:`paramiko.pkey.PKey` object as
279-
returned by :py:func:`pssh.utils.load_private_key`.
280+
Per host configuration can be provided for any or all of user, password
281+
port and private key. Private key value is a
282+
:py:class:`paramiko.pkey.PKey` object as returned by
283+
:py:func:`pssh.utils.load_private_key`.
280284
281-
:py:func:`pssh.utils.load_private_key` accepts both file names and file-like
282-
objects and will attempt to load all available key types, returning
283-
``None`` if they all fail.
285+
:py:func:`pssh.utils.load_private_key` accepts both file names and
286+
file-like objects and will attempt to load all available key types,
287+
returning ``None`` if they all fail.
284288
285289
.. code-block:: python
286290
@@ -768,19 +772,30 @@ def _update_host_output(self, output, host, exit_code, channel, stdout,
768772
exit_code=exit_code,
769773
exception=exception)
770774

771-
def join(self, output):
775+
def join(self, output, consume_output=False):
772776
"""Block until all remote commands in output have finished
773-
and retrieve exit codes"""
777+
and retrieve exit codes
778+
779+
:param output: Output of commands to join on
780+
:type output: dict as returned by
781+
:py:func:`pssh.pssh_client.ParallelSSHClient.get_output`
782+
"""
774783
for host in output:
775784
output[host].cmd.join()
776785
if output[host].channel is not None:
777786
output[host].channel.recv_exit_status()
787+
if consume_output:
788+
for line in output[host].stdout:
789+
pass
790+
for line in output[host].stderr:
791+
pass
778792
self.get_exit_codes(output)
779793

780794
def finished(self, output):
781795
"""Check if commands have finished without blocking
782796
783-
:param output: As returned by :py:func:`pssh.pssh_client.ParallelSSHClient.get_output`
797+
:param output: As returned by
798+
:py:func:`pssh.pssh_client.ParallelSSHClient.get_output`
784799
:rtype: bool
785800
"""
786801
for host in output:
@@ -793,7 +808,8 @@ def get_exit_codes(self, output):
793808
"""Get exit code for all hosts in output *if available*.
794809
Output parameter is modified in-place.
795810
796-
:param output: As returned by :py:func:`pssh.pssh_client.ParallelSSHClient.get_output`
811+
:param output: As returned by
812+
:py:func:`pssh.pssh_client.ParallelSSHClient.get_output`
797813
:rtype: None
798814
"""
799815
for host in output:

tests/test_pssh_client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ def tearDown(self):
8484
self.server.kill()
8585
del self.agent
8686

87+
def test_client_join_consume_output(self):
88+
output = self.client.run_command(self.fake_cmd)
89+
expected_exit_code = 0
90+
self.client.join(output, consume_output=True)
91+
exit_code = output[self.host]['exit_code']
92+
stdout = list(output[self.host]['stdout'])
93+
stderr = list(output[self.host]['stderr'])
94+
self.assertTrue(len(stdout) == 0)
95+
self.assertTrue(len(stderr) == 0)
96+
self.assertEqual(expected_exit_code, exit_code)
97+
8798
def test_client_join_stdout(self):
8899
output = self.client.run_command(self.fake_cmd)
89100
expected_exit_code = 0

0 commit comments

Comments
 (0)