Skip to content

Commit cf29d9d

Browse files
authored
Scp send rc (#340)
* Added SCP large file test. * Fix issue with scp_send - resolves #337 * Updated changelog * Updated embedded server, tests
1 parent 8e20f47 commit cf29d9d

File tree

6 files changed

+96
-38
lines changed

6 files changed

+96
-38
lines changed

.environment.yml

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ dependencies:
55
- setuptools
66
- pip
77
- toolchain3
8+
- cython

Changelog.rst

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ Changes
88
--------
99

1010
* ``pssh.exceptions.ConnectionError`` is now the same as built-in ``ConnectionError`` and deprecated - to be removed.
11-
* Clients now continue connecting with all addresses in DNS list. In the case where an address refuses connection,
11+
* Clients now attempt to connect with all addresses in DNS list. In the case where an address refuses connection,
1212
other available addresses are attempted without delay.
1313

1414
For example where a host resolves to both IPv4 and v6 addresses while only one address is
1515
accepting connections, or multiple v4/v6 addresses where only some are accepting connections.
1616
* Connection actively refused error is no longer subject to retries.
1717

18+
Fixes
19+
-----
20+
21+
* ``scp_send`` in native clients would sometimes fail to send all data in a race condition with client going out of scope.
22+
1823

1924
2.8.0
2025
+++++

pssh/clients/native/single.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ def _scp_recv(self, remote_file, local_file):
598598
total += size
599599
local_fh.write(data)
600600
finally:
601+
local_fh.flush()
601602
local_fh.close()
602603
file_chan.close()
603604

@@ -659,13 +660,16 @@ def _scp_send(self, local_file, remote_file):
659660
raise SCPError(msg, remote_file, self.host, ex)
660661
try:
661662
with open(local_file, 'rb', 2097152) as local_fh:
662-
for data in local_fh:
663+
data = local_fh.read(self._BUF_SIZE)
664+
while data:
663665
self.eagain_write(chan.write, data)
666+
data = local_fh.read(self._BUF_SIZE)
664667
except Exception as ex:
665668
msg = "Error writing to remote file %s on host %s - %s"
666669
logger.error(msg, remote_file, self.host, ex)
667670
raise SCPError(msg, remote_file, self.host, ex)
668671
finally:
672+
self._eagain(chan.flush)
669673
chan.close()
670674

671675
def _sftp_openfh(self, open_func, remote_file, *args):

tests/embedded_server/openssh.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,14 @@ def start_server(self):
8686
logger.debug("Starting server with %s" % (" ".join(cmd),))
8787
self.server_proc = Popen(cmd)
8888
try:
89-
self.server_proc.wait(.1)
89+
self.server_proc.wait(.3)
9090
except TimeoutExpired:
9191
pass
9292
else:
93-
logger.error(self.server_proc.stdout.read())
94-
logger.error(self.server_proc.stderr.read())
93+
if self.server_proc.stdout is not None:
94+
logger.error(self.server_proc.stdout.read())
95+
if self.server_proc.stderr is not None:
96+
logger.error(self.server_proc.stderr.read())
9597
raise Exception("Server could not start")
9698

9799
def stop(self):

tests/native/test_parallel_client.py

+63-5
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ def test_client_shells_read_timeout(self):
133133
def test_client_shells_timeout(self):
134134
client = ParallelSSHClient([self.host], pkey=self.user_key, port=self.port,
135135
timeout=0.01, num_retries=1)
136+
client._make_ssh_client = MagicMock()
137+
client._make_ssh_client.side_effect = Timeout
136138
self.assertRaises(Timeout, client.open_shell)
137139

138140
def test_client_shells_join_timeout(self):
@@ -1517,8 +1519,8 @@ def test_scp_send_dir_recurse(self):
15171519
except OSError:
15181520
pass
15191521

1520-
def test_scp_send_large_files_timeout(self):
1521-
hosts = ['127.0.0.1%s' % (i,) for i in range(1, 10)]
1522+
def test_scp_send_larger_files(self):
1523+
hosts = ['127.0.0.1%s' % (i,) for i in range(1, 3)]
15221524
servers = [OpenSSHServer(host, port=self.port) for host in hosts]
15231525
for server in servers:
15241526
server.start_server()
@@ -1535,7 +1537,7 @@ def test_scp_send_large_files_timeout(self):
15351537
remote_file_names = [arg['remote_file'] for arg in copy_args]
15361538
sha = sha256()
15371539
with open(local_filename, 'wb') as file_h:
1538-
for _ in range(5000):
1540+
for _ in range(10000):
15391541
data = os.urandom(1024)
15401542
file_h.write(data)
15411543
sha.update(data)
@@ -1547,13 +1549,15 @@ def test_scp_send_large_files_timeout(self):
15471549
except Exception:
15481550
raise
15491551
else:
1550-
sleep(.2)
1552+
del client
15511553
for remote_file_name in remote_file_names:
15521554
remote_file_abspath = os.path.expanduser('~/' + remote_file_name)
15531555
self.assertTrue(os.path.isfile(remote_file_abspath))
15541556
with open(remote_file_abspath, 'rb') as remote_fh:
1555-
for data in remote_fh:
1557+
data = remote_fh.read(10240)
1558+
while data:
15561559
sha.update(data)
1560+
data = remote_fh.read(10240)
15571561
remote_file_sha = sha.hexdigest()
15581562
sha = sha256()
15591563
self.assertEqual(source_file_sha, remote_file_sha)
@@ -1679,6 +1683,60 @@ def test_scp_recv(self):
16791683
except Exception:
16801684
pass
16811685

1686+
def test_scp_recv_larger_files(self):
1687+
hosts = ['127.0.0.1%s' % (i,) for i in range(1, 3)]
1688+
servers = [OpenSSHServer(host, port=self.port) for host in hosts]
1689+
for server in servers:
1690+
server.start_server()
1691+
client = ParallelSSHClient(
1692+
hosts, port=self.port, pkey=self.user_key, num_retries=1, timeout=1,
1693+
pool_size=len(hosts),
1694+
)
1695+
dir_name = os.path.dirname(__file__)
1696+
remote_filename = 'test_file'
1697+
remote_filepath = os.path.join(dir_name, remote_filename)
1698+
local_filename = 'file_copy'
1699+
copy_args = [{
1700+
'remote_file': remote_filepath,
1701+
'local_file': os.path.expanduser("~/" + 'host_%s_%s' % (n, local_filename))}
1702+
for n in range(len(hosts))
1703+
]
1704+
local_file_names = [
1705+
arg['local_file'] for arg in copy_args]
1706+
sha = sha256()
1707+
with open(remote_filepath, 'wb') as file_h:
1708+
for _ in range(10000):
1709+
data = os.urandom(1024)
1710+
file_h.write(data)
1711+
sha.update(data)
1712+
file_h.flush()
1713+
source_file_sha = sha.hexdigest()
1714+
sha = sha256()
1715+
cmds = client.scp_recv('%(remote_file)s', '%(local_file)s', copy_args=copy_args)
1716+
try:
1717+
joinall(cmds, raise_error=True)
1718+
except Exception:
1719+
raise
1720+
else:
1721+
del client
1722+
for _local_file_name in local_file_names:
1723+
self.assertTrue(os.path.isfile(_local_file_name))
1724+
with open(_local_file_name, 'rb') as fh:
1725+
data = fh.read(10240)
1726+
while data:
1727+
sha.update(data)
1728+
data = fh.read(10240)
1729+
local_file_sha = sha.hexdigest()
1730+
sha = sha256()
1731+
self.assertEqual(source_file_sha, local_file_sha)
1732+
finally:
1733+
try:
1734+
os.unlink(remote_filepath)
1735+
for _local_file_name in local_file_names:
1736+
os.unlink(_local_file_name)
1737+
except OSError:
1738+
pass
1739+
16821740
def test_bad_hosts_value(self):
16831741
self.assertRaises(TypeError, ParallelSSHClient, 'a host')
16841742
self.assertRaises(TypeError, ParallelSSHClient, b'a host')

tests/native/test_single_client.py

+16-28
Original file line numberDiff line numberDiff line change
@@ -405,10 +405,10 @@ def test_auth_retry_failure(self):
405405

406406
def test_connection_timeout(self):
407407
cmd = spawn(SSHClient, 'fakehost.com', port=12345,
408-
num_retries=1, timeout=1, _auth_thread_pool=False)
408+
num_retries=1, timeout=.1, _auth_thread_pool=False)
409409
# Should fail within greenlet timeout, otherwise greenlet will
410410
# raise timeout which will fail the test
411-
self.assertRaises(ConnectionErrorException, cmd.get, timeout=2)
411+
self.assertRaises(ConnectionErrorException, cmd.get, timeout=1)
412412

413413
def test_client_read_timeout(self):
414414
client = SSHClient(self.host, port=self.port,
@@ -657,27 +657,22 @@ def test_scp_recv_large_file(self):
657657
os.unlink(_path)
658658
except OSError:
659659
pass
660+
sha = sha256()
660661
try:
661662
with open(file_path_from, 'wb') as fh:
662-
# ~300MB
663-
for _ in range(20000000):
664-
fh.write(b"adsfasldkfjabafj")
663+
for _ in range(10000):
664+
data = os.urandom(1024)
665+
fh.write(data)
666+
sha.update(data)
667+
source_file_sha = sha.hexdigest()
665668
self.client.scp_recv(file_path_from, file_copy_to_dirpath)
666669
self.assertTrue(os.path.isfile(file_copy_to_dirpath))
667-
read_file_size = os.stat(file_path_from).st_size
668-
written_file_size = os.stat(file_copy_to_dirpath).st_size
669-
self.assertEqual(read_file_size, written_file_size)
670-
sha = sha256()
671-
with open(file_path_from, 'rb') as fh:
672-
for block in fh:
673-
sha.update(block)
674-
read_file_hash = sha.hexdigest()
675670
sha = sha256()
676671
with open(file_copy_to_dirpath, 'rb') as fh:
677672
for block in fh:
678673
sha.update(block)
679674
written_file_hash = sha.hexdigest()
680-
self.assertEqual(read_file_hash, written_file_hash)
675+
self.assertEqual(source_file_sha, written_file_hash)
681676
finally:
682677
for _path in (file_path_from, file_copy_to_dirpath):
683678
try:
@@ -728,29 +723,22 @@ def test_scp_send_large_file(self):
728723
os.unlink(_path)
729724
except OSError:
730725
pass
726+
sha = sha256()
731727
try:
732728
with open(file_path_from, 'wb') as fh:
733-
# ~300MB
734-
for _ in range(20000000):
735-
fh.write(b"adsfasldkfjabafj")
729+
for _ in range(10000):
730+
data = os.urandom(1024)
731+
fh.write(data)
732+
sha.update(data)
733+
source_file_sha = sha.hexdigest()
736734
self.client.scp_send(file_path_from, file_copy_to_dirpath)
737735
self.assertTrue(os.path.isfile(file_copy_to_dirpath))
738-
# OS file flush race condition
739-
sleep(.1)
740-
read_file_size = os.stat(file_path_from).st_size
741-
written_file_size = os.stat(file_copy_to_dirpath).st_size
742-
self.assertEqual(read_file_size, written_file_size)
743-
sha = sha256()
744-
with open(file_path_from, 'rb') as fh:
745-
for block in fh:
746-
sha.update(block)
747-
read_file_hash = sha.hexdigest()
748736
sha = sha256()
749737
with open(file_copy_to_dirpath, 'rb') as fh:
750738
for block in fh:
751739
sha.update(block)
752740
written_file_hash = sha.hexdigest()
753-
self.assertEqual(read_file_hash, written_file_hash)
741+
self.assertEqual(source_file_sha, written_file_hash)
754742
finally:
755743
for _path in (file_path_from, file_copy_to_dirpath):
756744
try:

0 commit comments

Comments
 (0)