Repository: libcloud Updated Branches: refs/heads/trunk bd17cb936 -> 514cbb334
Fix an edge case bug when consuming data from a channel inside the Paramiko SSH client. In case consumed data contained multi byte UTF-8 character and this character was split over two chunks, an exception would be thrown since we tried to decode received data after receiving every chunk instead of the end. This bug was originally caught by Lakshmi Kannan and fix is partially based on the changes inside StackStorm/st2 which are Apache 2.0 licensed. Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/1a658097 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/1a658097 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/1a658097 Branch: refs/heads/trunk Commit: 1a6580973ff0b794d0dfc02ec76c27eac7b4671f Parents: 2b3beef Author: Tomaz Muraus <[email protected]> Authored: Sun Jan 24 21:12:49 2016 +0100 Committer: Tomaz Muraus <[email protected]> Committed: Sun Jan 24 21:12:49 2016 +0100 ---------------------------------------------------------------------- libcloud/compute/ssh.py | 51 ++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 21 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/1a658097/libcloud/compute/ssh.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/ssh.py b/libcloud/compute/ssh.py index 3143d5d..b0ccffd 100644 --- a/libcloud/compute/ssh.py +++ b/libcloud/compute/ssh.py @@ -201,6 +201,7 @@ class ParamikoSSHClient(BaseSSHClient): # Maximum number of bytes to read at once from a socket CHUNK_SIZE = 1024 + # How long to sleep while waiting for command to finish SLEEP_DELAY = 1.5 @@ -402,41 +403,49 @@ class ParamikoSSHClient(BaseSSHClient): """ Try to consume stdout data from chan if it's receive ready. """ - - stdout = StringIO() - if chan.recv_ready(): - data = chan.recv(self.CHUNK_SIZE) - - while data: - stdout.write(b(data).decode('utf-8')) - ready = chan.recv_ready() - - if not ready: - break - - data = chan.recv(self.CHUNK_SIZE) - + stdout = self._consume_data_from_channel( + chan=chan, + recv_method=chan.recv, + recv_ready_method=chan.recv_ready) return stdout def _consume_stderr(self, chan): """ Try to consume stderr data from chan if it's receive ready. """ + stderr = self._consume_data_from_channel( + chan=chan, + recv_method=chan.recv_stderr, + recv_ready_method=chan.recv_stderr_ready) + return stderr - stderr = StringIO() - if chan.recv_stderr_ready(): - data = chan.recv_stderr(self.CHUNK_SIZE) + def _consume_data_from_channel(self, chan, recv_method, recv_ready_method): + """ + Try to consume data from the provided channel. + + Keep in mind that data is only consumed if the channel is receive + ready. + """ + result = StringIO() + result_bytes = bytearray() + + if recv_ready_method(): + data = recv_method(self.CHUNK_SIZE) + result_bytes += b(data) while data: - stderr.write(b(data).decode('utf-8')) - ready = chan.recv_stderr_ready() + ready = recv_ready_method() if not ready: break - data = chan.recv_stderr(self.CHUNK_SIZE) + data = recv_method(self.CHUNK_SIZE) - return stderr + # We only decode data at the end because a single chunk could contain + # a part of multi byte UTF-8 character (whole multi bytes character + # could be split over two chunks) + result.write(result_bytes.decode('utf-8')) + return result def _get_pkey_object(self, key): """
