Skip to content

Commit 56ec42e

Browse files
author
Dan
committed
Cleanup. Updated docstrings. Fixed missing pty regression. Added pty test for ssh client. Updated timeout test to check requested timeout against timeout set in channel.
1 parent 3692f4b commit 56ec42e

File tree

3 files changed

+50
-23
lines changed

3 files changed

+50
-23
lines changed

pssh.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,16 @@ def __init__(self, host,
147147
if self.proxy_host and self.proxy_port:
148148
logger.debug("Proxy configured for destination host %s - Proxy host: %s:%s",
149149
self.host, self.proxy_host, self.proxy_port,)
150-
self._connect_proxy()
150+
self._connect_tunnel()
151151
else:
152152
self._connect(self.client, self.host, self.port)
153153

154-
def _connect_proxy(self):
155-
"""Connects to SSH server and returns transport channel to be used
156-
to forward proxy to another SSH server.
157-
client (me) -> proxy (ssh server to proxy through) -> \
154+
def _connect_tunnel(self):
155+
"""Connects to SSH server via an intermediate SSH tunnel server.
156+
client (me) -> tunnel (ssh server to proxy through) -> \
158157
destination (ssh server to run command)
159-
:rtype: `:mod:paramiko.Channel` Channel to proxy through"""
158+
:rtype: `:mod:paramiko.SSHClient` Client to remote SSH destination
159+
via intermediate SSH tunnel server."""
160160
self.proxy_client = paramiko.SSHClient()
161161
self.proxy_client.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy())
162162
self._connect(self.proxy_client, self.proxy_host, self.proxy_port)
@@ -168,44 +168,51 @@ def _connect_proxy(self):
168168
return self._connect(self.client, self.host, self.port, sock=proxy_channel)
169169

170170
def _connect(self, client, host, port, sock=None, retries=1):
171-
"""Connect to host, throw UnknownHost exception on DNS errors"""
171+
"""Connect to host
172+
173+
:raises: :mod:`pssh.AuthenticationException` on authentication error
174+
:raises: :mod:`pssh.UnknownHostException` on DNS resolution error
175+
:raises: :mod:`pssh.ConnectionErrorException` on error connecting
176+
:raises: :mod:`pssh.SSHException` on other undefined SSH errors
177+
"""
172178
try:
173179
client.connect(host, username=self.user,
174180
password=self.password, port=port,
175181
pkey=self.pkey,
176182
sock=sock, timeout=self.timeout)
177-
except sock_gaierror, e:
183+
except sock_gaierror, ex:
178184
logger.error("Could not resolve host '%s' - retry %s/%s",
179185
self.host, retries, self.num_retries)
180186
while retries < self.num_retries:
181187
gevent.sleep(5)
182188
return self._connect(client, host, port, sock=sock,
183189
retries=retries+1)
184190
raise UnknownHostException("%s - %s - retry %s/%s",
185-
str(e.args[1]),
191+
str(ex.args[1]),
186192
self.host, retries, self.num_retries)
187-
except sock_error, e:
193+
except sock_error, ex:
188194
logger.error("Error connecting to host '%s:%s' - retry %s/%s",
189195
self.host, self.port, retries, self.num_retries)
190196
while retries < self.num_retries:
191197
gevent.sleep(5)
192198
return self._connect(client, host, port, sock=sock,
193199
retries=retries+1)
194-
error_type = e.args[1] if len(e.args) > 1 else e.args[0]
200+
error_type = ex.args[1] if len(ex.args) > 1 else ex.args[0]
195201
raise ConnectionErrorException("%s for host '%s:%s' - retry %s/%s",
196202
str(error_type), self.host, self.port,
197203
retries, self.num_retries,)
198-
except paramiko.AuthenticationException, e:
199-
raise AuthenticationException(e)
204+
except paramiko.AuthenticationException, ex:
205+
raise AuthenticationException(ex)
200206
# SSHException is more general so should be below other types
201207
# of SSH failure
202-
except paramiko.SSHException, e:
203-
raise SSHException(e)
208+
except paramiko.SSHException, ex:
209+
logger.error("General SSH error - %s", ex)
210+
raise SSHException(ex)
204211

205212
def exec_command(self, command, sudo=False, user=None, **kwargs):
206213
"""Wrapper to :mod:`paramiko.SSHClient.exec_command`
207214
208-
Opens a new SSH session with a pty and runs command with given \
215+
Opens a new SSH session with a new pty and runs command with given \
209216
`kwargs` if any. Greenlet then yields (sleeps) while waiting for \
210217
command to finish executing or channel to close indicating the same.
211218
@@ -224,7 +231,7 @@ def exec_command(self, command, sudo=False, user=None, **kwargs):
224231
channel = self.client.get_transport().open_session()
225232
if self.forward_ssh_agent:
226233
agent_handler = paramiko.agent.AgentRequestHandler(channel)
227-
# channel.get_pty()
234+
channel.get_pty()
228235
if self.timeout:
229236
channel.settimeout(self.timeout)
230237
_stdout, _stderr = channel.makefile('rb'), \

tests/test_pssh_client.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,27 +191,32 @@ def test_pssh_client_auth_failure(self):
191191
server.join()
192192

193193
def test_pssh_client_timeout(self):
194+
server_timeout=0.2
195+
client_timeout=server_timeout-0.1
194196
server = start_server({ self.fake_cmd : self.fake_resp },
195197
self.listen_socket,
196-
timeout=0.2)
198+
timeout=server_timeout)
197199
client = ParallelSSHClient(['127.0.0.1'], port=self.listen_port,
198200
pkey=self.user_key,
199-
timeout=0.1)
200-
cmd = client.exec_command(self.fake_cmd)[0]
201+
timeout=client_timeout)
202+
output = client.run_command(self.fake_cmd)
201203
# Handle exception
202204
try:
203-
gevent.sleep(0.5)
204-
cmd.get()
205+
gevent.sleep(server_timeout+0.2)
206+
client.pool.join()
205207
if not server.exception:
206208
raise Exception(
207209
"Expected gevent.Timeout from socket timeout, got none")
208210
raise server.exception
209211
except gevent.Timeout:
210212
pass
213+
chan_timeout = output['127.0.0.1']['channel'].gettimeout()
214+
self.assertEqual(client_timeout, chan_timeout,
215+
msg="Channel timeout %s does not match requested timeout %s" %(
216+
chan_timeout, client_timeout,))
211217
del client
212218
server.join()
213219

214-
215220
def test_pssh_client_exec_command_password(self):
216221
"""Test password authentication. Fake server accepts any password
217222
even empty string"""

tests/test_ssh_client.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,21 @@ def test_ssh_client_unknown_host_failure(self):
136136
SSHClient, host, port=self.listen_port,
137137
pkey=self.user_key, num_retries=0)
138138

139+
def test_ssh_client_pty(self):
140+
"""Test that we get a new pty for our non-interactive SSH sessions"""
141+
server = start_server({ self.fake_cmd : self.fake_resp },
142+
self.listen_socket)
143+
client = SSHClient('127.0.0.1', port=self.listen_port)
144+
channel = client.client.get_transport().open_session()
145+
self.assertFalse(channel.event.is_set(),
146+
msg="Got pty without requesting it")
147+
channel.get_pty()
148+
self.assertTrue(channel.event.is_set(),
149+
msg="Requested pty but got none")
150+
channel.close()
151+
del channel
152+
del client
153+
server.join()
139154

140155
if __name__ == '__main__':
141156
unittest.main()

0 commit comments

Comments
 (0)