Skip to content

Commit 8dd2f3e

Browse files
authored
Scp send copy args (#232)
* Added copy args functionality to scp_send. * Updated changelog. * Added large file scp_send test to multiple remote servers.
1 parent 28064e9 commit 8dd2f3e

File tree

4 files changed

+92
-19
lines changed

4 files changed

+92
-19
lines changed

Changelog.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ See `Upgrading to API 2.0 <https://parallel-ssh.readthedocs.io/en/latest/api_upg
2727
* Removed ``run_command(greenlet_timeout=<..>)`` argument - now uses global timeout setting.
2828
* Renamed ``run_command`` ``timeout`` to ``read_timeout=<seconds>)`` for setting output read timeout individually - defaults to global timeout setting.
2929
* Removed ``pssh.native`` package and native code.
30-
* No native code means package architecture has changed to ``none-any``.
30+
* ``ParallelSSHClient.scp_send`` now supports ``copy_args`` keyword argument for providing per-host file name arguments like rest of ``scp_*`` and ``copy_*`` functionality.
3131

3232

3333
Fixes
@@ -38,6 +38,12 @@ Fixes
3838
* Clients could raise ``Timeout`` early when timeout settings were used with many hosts.
3939

4040

41+
Packaging
42+
---------
43+
44+
* Package architecture has changed to ``none-any``.
45+
46+
4147
1.13.0
4248
++++++
4349

doc/advanced.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,8 @@ Configurable per host Filenames
512512

513513
File name arguments, for both local and remote files and for copying to and from remote hosts, can be configured on a per-host basis similarly to `host arguments <#per-host-cmds>`_ in ``run_command``.
514514

515+
Example shown applies to all file copy functionality, all of ``scp_send``, ``scp_recv``, ``copy_file`` and ``copy_remote_file``.
516+
515517
For example, to copy the local files ``['local_file_1', 'local_file_2']`` as remote files ``['remote_file_1', 'remote_file_2']`` on the two hosts ``['host1', 'host2']``
516518

517519
.. code-block:: python
@@ -532,9 +534,9 @@ For example, to copy the local files ``['local_file_1', 'local_file_2']`` as rem
532534
533535
The client will copy ``local_file_1`` to ``host1`` as ``remote_file_1`` and ``local_file_2`` to ``host2`` as ``remote_file_2``.
534536

535-
Items in ``copy_args`` list may be tuples or dictionaries as shown above. Number of ``copy_args`` must match length of ``client.hosts`` if provided.
537+
Each item in ``copy_args`` list should be a dictionary as shown above. Number of ``copy_args`` must match length of ``client.hosts`` if provided or exception will be raised.
536538

537-
``copy_remote_file`` may be used in the same manner to configure remote and local file names per host.
539+
``copy_remote_file``, ``scp_send`` and ``scp_recv`` may all be used in the same manner to configure remote and local file names per host.
538540

539541
.. seealso::
540542

pssh/clients/native/parallel.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ def _scp_recv(self, host_i, host, remote_file, local_file, recurse=False):
437437
self._host_clients[(host_i, host)].scp_recv, host,
438438
remote_file, local_file, recurse=recurse)
439439

440-
def scp_send(self, local_file, remote_file, recurse=False):
440+
def scp_send(self, local_file, remote_file, recurse=False, copy_args=None):
441441
"""Copy local file to remote file in parallel via SCP.
442442
443443
This function returns a list of greenlets which can be
@@ -469,9 +469,22 @@ def scp_send(self, local_file, remote_file, recurse=False):
469469
:raises: :py:class:`pss.exceptions.SCPError` on errors copying file.
470470
:raises: :py:class:`OSError` on local OS errors like permission denied.
471471
"""
472-
return [self.pool.spawn(self._scp_send, host_i, host, local_file,
473-
remote_file, recurse=recurse)
474-
for host_i, host in enumerate(self.hosts)]
472+
copy_args = [{'local_file': local_file,
473+
'remote_file': remote_file}
474+
for i, host in enumerate(self.hosts)] \
475+
if copy_args is None else copy_args
476+
local_file = "%(local_file)s"
477+
remote_file = "%(remote_file)s"
478+
try:
479+
return [self.pool.spawn(self._scp_send, host_i, host,
480+
local_file % copy_args[host_i],
481+
remote_file % copy_args[host_i],
482+
recurse=recurse)
483+
for host_i, host in enumerate(self.hosts)]
484+
except IndexError:
485+
raise HostArgumentException(
486+
"Number of per-host copy arguments provided does not match "
487+
"number of hosts")
475488

476489
def scp_recv(self, remote_file, local_file, recurse=False, copy_args=None,
477490
suffix_separator='_'):

tests/native/test_parallel_client.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@
2626
import sys
2727
import string
2828
import random
29+
from hashlib import sha256
2930
from datetime import datetime
3031
from platform import python_version
3132

32-
from pytest import mark
3333
from gevent import joinall, spawn, socket, Greenlet, sleep
3434
from pssh.config import HostConfig
3535
from pssh.clients.native import ParallelSSHClient
@@ -1354,29 +1354,81 @@ def test_scp_send_dir_recurse(self):
13541354
pass
13551355

13561356
def test_scp_send(self):
1357-
test_file_data = 'test'
1357+
server2_host = '127.0.0.11'
1358+
server3_host = '127.0.0.12'
1359+
server2 = OpenSSHServer(server2_host, port=self.port)
1360+
server3 = OpenSSHServer(server3_host, port=self.port)
1361+
for server in (server2, server3):
1362+
server.start_server()
1363+
hosts = [self.host, server2_host, server3_host]
1364+
client = ParallelSSHClient(hosts, port=self.port, pkey=self.user_key, num_retries=1)
13581365
local_filename = 'test_file'
1359-
remote_test_dir, remote_filepath = 'remote_test_dir', 'test_file_copy'
1360-
with open(local_filename, 'w') as file_h:
1361-
file_h.writelines([test_file_data + os.linesep])
1362-
remote_file_abspath = os.path.expanduser('~/' + remote_filepath)
1363-
cmds = self.client.scp_send(local_filename, remote_filepath)
1366+
remote_filepath = 'file_copy'
1367+
copy_args = [{
1368+
'local_file': local_filename,
1369+
'remote_file': 'host_%s_%s' % (n, remote_filepath)}
1370+
for n in range(len(hosts))]
1371+
remote_file_names = [arg['remote_file'] for arg in copy_args]
1372+
sha = sha256()
1373+
with open(local_filename, 'wb') as file_h:
1374+
for _ in range(10000):
1375+
data = os.urandom(1024)
1376+
file_h.write(data)
1377+
sha.update(data)
1378+
source_file_sha = sha.hexdigest()
1379+
sha = sha256()
1380+
cmds = client.scp_send('%(local_file)s', '%(remote_file)s', copy_args=copy_args)
13641381
try:
13651382
joinall(cmds, raise_error=True)
13661383
except Exception:
13671384
raise
13681385
else:
1369-
self.assertTrue(os.path.isfile(remote_file_abspath))
1370-
remote_contents = open(remote_file_abspath, 'rb').read()
1371-
local_contents = open(local_filename, 'rb').read()
1372-
self.assertEqual(local_contents, remote_contents)
1386+
sleep(.2)
1387+
for remote_file_name in remote_file_names:
1388+
remote_file_abspath = os.path.expanduser('~/' + remote_file_name)
1389+
self.assertTrue(os.path.isfile(remote_file_abspath))
1390+
with open(remote_file_abspath, 'rb') as remote_fh:
1391+
for data in remote_fh:
1392+
sha.update(data)
1393+
remote_file_sha = sha.hexdigest()
1394+
sha = sha256()
1395+
self.assertEqual(source_file_sha, remote_file_sha)
13731396
finally:
13741397
try:
13751398
os.unlink(local_filename)
1376-
os.unlink(remote_file_abspath)
1399+
for remote_file_name in remote_file_names:
1400+
remote_file_abspath = os.path.expanduser('~/' + remote_file_name)
1401+
os.unlink(remote_file_abspath)
13771402
except OSError:
13781403
pass
13791404

1405+
def test_scp_send_bad_copy_args(self):
1406+
client = ParallelSSHClient([self.host, self.host])
1407+
copy_args = [{'local_file': 'test', 'remote_file': 'test'}]
1408+
self.assertRaises(HostArgumentException,
1409+
client.scp_send, '%(local_file)s', '%(remote_file)s',
1410+
copy_args=copy_args)
1411+
1412+
def test_scp_send_exc(self):
1413+
client = ParallelSSHClient([self.host], pkey=self.user_key, num_retries=1)
1414+
def _scp_send(*args):
1415+
raise Exception
1416+
def _client_send(*args):
1417+
return client._handle_greenlet_exc(_scp_send, 'fake')
1418+
client._scp_send = _client_send
1419+
cmds = client.scp_send('local_file', 'remote_file')
1420+
self.assertRaises(Exception, joinall, cmds, raise_error=True)
1421+
1422+
def test_scp_recv_exc(self):
1423+
client = ParallelSSHClient([self.host], pkey=self.user_key, num_retries=1)
1424+
def _scp_recv(*args):
1425+
raise Exception
1426+
def _client_recv(*args):
1427+
return client._handle_greenlet_exc(_scp_recv, 'fake')
1428+
client._scp_recv = _client_recv
1429+
cmds = client.scp_recv('remote_file', 'local_file')
1430+
self.assertRaises(Exception, joinall, cmds, raise_error=True)
1431+
13801432
def test_scp_recv_failure(self):
13811433
cmds = self.client.scp_recv(
13821434
'fakey fakey fake fake', 'equally fake')

0 commit comments

Comments
 (0)