summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbuildscripts/remote_operations.py324
-rwxr-xr-xbuildscripts/tests/test_remote_operations.py344
2 files changed, 668 insertions, 0 deletions
diff --git a/buildscripts/remote_operations.py b/buildscripts/remote_operations.py
new file mode 100755
index 00000000000..162f45629dc
--- /dev/null
+++ b/buildscripts/remote_operations.py
@@ -0,0 +1,324 @@
+#!/usr/bin/env python
+
+"""Remote access utilities, via ssh & scp."""
+
+from __future__ import print_function
+
+import optparse
+import os
+import re
+import shlex
+import sys
+import time
+
+# The subprocess32 module is untested on Windows and thus isn't recommended for use, even when it's
+# installed. See https://github.com/google/python-subprocess32/blob/3.2.7/README.md#usage.
+if os.name == "posix" and sys.version_info[0] == 2:
+ try:
+ import subprocess32 as subprocess
+ except ImportError:
+ import warnings
+ warnings.warn(("Falling back to using the subprocess module because subprocess32 isn't"
+ " available. When using the subprocess module, a child process may trigger"
+ " an invalid free(). See SERVER-22219 for more details."),
+ RuntimeWarning)
+ import subprocess
+else:
+ import subprocess
+
+# Get relative imports to work when the package is not installed on the PYTHONPATH.
+if __name__ == "__main__" and __package__ is None:
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+_OPERATIONS = ["shell", "copy_to", "copy_from"]
+
+
+class RemoteOperations(object):
+ """Class to support remote operations."""
+
+ def __init__(self,
+ user_host,
+ ssh_options="",
+ retries=0,
+ retry_sleep=0,
+ debug=False,
+ use_shell=False):
+
+ self.user_host = user_host
+ self.ssh_options = ssh_options if ssh_options else ""
+ self.retries = retries
+ self.retry_sleep = retry_sleep
+ self.debug = debug
+ self.use_shell = use_shell
+ # Check if we can remotely access the host.
+ self._access_code, self._access_buff = self._remote_access()
+
+ def _call(self, cmd):
+ if self.debug:
+ print(cmd)
+ # If use_shell is False we need to split the command up into a list.
+ if not self.use_shell:
+ cmd = shlex.split(cmd)
+ # Use a common pipe for stdout & stderr for logging.
+ process = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ shell=self.use_shell)
+ buff_stdout, buff_stderr = process.communicate()
+ return process.poll(), buff_stdout
+
+ def _remote_access(self):
+ """ This will check if a remote session is possible. """
+ cmd = "ssh {} {} date".format(self.ssh_options, self.user_host)
+ attempt_num = 0
+ buff = ""
+ while True:
+ ret, buff = self._call(cmd)
+ if not ret:
+ return ret, buff
+ attempt_num += 1
+ if attempt_num > self.retries:
+ break
+ if self.debug:
+ print("Failed remote attempt {}, retrying in {} seconds".format(
+ attempt_num, self.retry_sleep))
+ time.sleep(self.retry_sleep)
+ return ret, buff
+
+ def _perform_operation(self, cmd):
+ return self._call(cmd)
+
+ def access_established(self):
+ """ Returns True if initial access was establsished. """
+ return not self._access_code
+
+ def access_info(self):
+ """ Returns return code and output buffer from initial access attempt(s). """
+ return self._access_code, self._access_buff
+
+ def operation(self, operation_type, operation_param, operation_dir=None):
+ """ Main entry for remote operations. Returns (code, output).
+
+ 'operation_type' supports remote shell and copy operations.
+ 'operation_param' can either be a list or string of commands or files.
+ 'operation_dir' is '.' if unspecified for 'copy_*'.
+ """
+
+ if not self.access_established():
+ return self.access_info()
+
+ # File names with a space must be quoted, since we permit the
+ # the file names to be either a string or a list.
+ if operation_type != "shell" and isinstance(operation_param, str):
+ operation_param = shlex.split(operation_param)
+
+ cmds = []
+ if operation_type == "shell":
+ if operation_dir is not None:
+ operation_param = "cd {}; {}".format(operation_dir, operation_param)
+ cmd = "ssh {} {} '{}'".format(self.ssh_options, self.user_host, operation_param)
+ cmds.append(cmd)
+
+ elif operation_type == "copy_to":
+ cmd = "scp -r {}".format(self.ssh_options)
+ # To support spaces in the filename or directory, we quote them one at a time.
+ for file in operation_param:
+ cmd += "\"{}\" ".format(file)
+ operation_dir = operation_dir if operation_dir else ""
+ cmd += " {}:{}".format(self.user_host, operation_dir)
+ cmds.append(cmd)
+
+ elif operation_type == "copy_from":
+ operation_dir = operation_dir if operation_dir else "."
+ if not os.path.isdir(operation_dir):
+ raise ValueError(
+ "Local directory '{}' does not exist.".format(operation_dir))
+
+ # We support multiple files being copied from the remote host
+ # by invoking scp for each file specified.
+ # Note - this is a method which scp does not support directly.
+ for file in operation_param:
+ cmd = "scp -r {} {}:".format(self.ssh_options, self.user_host)
+ # Quote, and escape the file if there are spaces.
+ # Note - we do not support other non-ASCII characters in a file name.
+ if " " in file:
+ file = re.escape("\"{}\"".format(file))
+ cmd += "{} {}".format(file, operation_dir)
+ cmds.append(cmd)
+
+ else:
+ raise ValueError(
+ "Invalid operation '{}' specified, choose from {}.".format(
+ operation_type, _OPERATIONS))
+
+ final_ret = 0
+ for cmd in cmds:
+ ret, buff = self._perform_operation(cmd)
+ buff += buff
+ final_ret = final_ret or ret
+
+ return final_ret, buff
+
+ def shell(self, operation_param, operation_dir=None):
+ """ Helper for remote shell operations. """
+ return self.operation(
+ operation_type="shell",
+ operation_param=operation_param,
+ operation_dir=operation_dir)
+
+ def copy_to(self, operation_param, operation_dir=None):
+ """ Helper for remote copy_to operations. """
+ return self.operation(
+ operation_type="copy_to",
+ operation_param=operation_param,
+ operation_dir=operation_dir)
+
+ def copy_from(self, operation_param, operation_dir=None):
+ """ Helper for remote copy_from operations. """
+ return self.operation(
+ operation_type="copy_from",
+ operation_param=operation_param,
+ operation_dir=operation_dir)
+
+
+def main():
+
+ parser = optparse.OptionParser(description=__doc__)
+ control_options = optparse.OptionGroup(parser, "Control options")
+ shell_options = optparse.OptionGroup(parser, "Shell options")
+ copy_options = optparse.OptionGroup(parser, "Copy options")
+
+ parser.add_option("--userHost",
+ dest="user_host",
+ default=None,
+ help="User and remote host to execute commands on [REQUIRED]."
+ " Examples, 'user@1.2.3.4' or 'user@myhost.com'.")
+
+ parser.add_option("--operation",
+ dest="operation",
+ default="shell",
+ choices=_OPERATIONS,
+ help="Remote operation to perform, choose one of '{}',"
+ " defaults to '%default'.".format(", ".join(_OPERATIONS)))
+
+ control_options.add_option("--sshOptions",
+ dest="ssh_options",
+ default=None,
+ action="append",
+ help="SSH connection options."
+ " More than one option can be specified either"
+ " in one quoted string or by specifying"
+ " this option more than once. Example options:"
+ " '-i $HOME/.ssh/access.pem -o ConnectTimeout=10"
+ " -o ConnectionAttempts=10'")
+
+ control_options.add_option("--retries",
+ dest="retries",
+ type=int,
+ default=0,
+ help="Number of retries to attempt for operation,"
+ " defaults to '%default'.")
+
+ control_options.add_option("--retrySleep",
+ dest="retry_sleep",
+ type=int,
+ default=10,
+ help="Number of seconds to wait between retries,"
+ " defaults to '%default'.")
+
+ control_options.add_option("--debug",
+ dest="debug",
+ action="store_true",
+ default=False,
+ help="Provides debug output.")
+
+ control_options.add_option("--verbose",
+ dest="verbose",
+ action="store_true",
+ default=False,
+ help="Print exit status and output at end.")
+
+ shell_options.add_option("--commands",
+ dest="remote_commands",
+ default=None,
+ action="append",
+ help="Commands to excute on the remote host. The"
+ " commands must be separated by a ';' and can either"
+ " be specifed in a quoted string or by specifying"
+ " this option more than once. A ';' will be added"
+ " between commands when this option is specifed"
+ " more than once.")
+
+ shell_options.add_option("--commandDir",
+ dest="command_dir",
+ default=None,
+ help="Working directory on remote to execute commands"
+ " form. Defaults to remote login directory.")
+
+ copy_options.add_option("--file",
+ dest="files",
+ default=None,
+ action="append",
+ help="The file to copy to/from remote host. To"
+ " support spaces in the file, each file must be"
+ " specified using this option more than once.")
+
+ copy_options.add_option("--remoteDir",
+ dest="remote_dir",
+ default=None,
+ help="Remote directory to copy to, only applies when"
+ " operation is 'copy_to'. Defaults to the login"
+ " directory on the remote host.")
+
+ copy_options.add_option("--localDir",
+ dest="local_dir",
+ default=".",
+ help="Local directory to copy to, only applies when"
+ " operation is 'copy_from'. Defaults to the"
+ " current directory, '%default'.")
+
+ parser.add_option_group(control_options)
+ parser.add_option_group(shell_options)
+ parser.add_option_group(copy_options)
+
+ (options, args) = parser.parse_args()
+
+ if not getattr(options, "user_host", None):
+ parser.print_help()
+ parser.error("Missing required option")
+
+ if options.operation == "shell":
+ if not getattr(options, "remote_commands", None):
+ parser.print_help()
+ parser.error("Missing required '{}' option '{}'".format(
+ options.operation, "--commands"))
+ operation_param = ";".join(options.remote_commands)
+ operation_dir = options.command_dir
+ else:
+ if not getattr(options, "files", None):
+ parser.print_help()
+ parser.error("Missing required '{}' option '{}'".format(
+ options.operation, "--file"))
+ operation_param = options.files
+ if options.operation == "copy_to":
+ operation_dir = options.remote_dir
+ else:
+ operation_dir = options.local_dir
+
+ ssh_options = None if not options.ssh_options else " ".join(options.ssh_options)
+ remote_op = RemoteOperations(
+ user_host=options.user_host,
+ ssh_options=ssh_options,
+ retries=options.retries,
+ retry_sleep=options.retry_sleep,
+ debug=options.debug)
+ ret_code, buffer = remote_op.operation(options.operation, operation_param, operation_dir)
+ if options.verbose:
+ print("Return code: {} for command {}".format(ret_code, sys.argv))
+ print(buffer)
+
+ sys.exit(ret_code)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/buildscripts/tests/test_remote_operations.py b/buildscripts/tests/test_remote_operations.py
new file mode 100755
index 00000000000..cae02865d71
--- /dev/null
+++ b/buildscripts/tests/test_remote_operations.py
@@ -0,0 +1,344 @@
+#!/usr/bin/env python
+
+"""Unit test for buildscripts/remote_operations.py.
+
+ Note - Tests require sshd to be enabled on localhost with paswordless login."""
+
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+
+if __name__ == "__main__" and __package__ is None:
+ sys.path.append(os.getcwd())
+from buildscripts import remote_operations as rop
+
+
+class RemoteOperationsTestCase(unittest.TestCase):
+ def setUp(self):
+ self.temp_local_dir = tempfile.mkdtemp()
+ self.temp_remote_dir = tempfile.mkdtemp()
+ self.rop = rop.RemoteOperations(user_host="localhost")
+ self.rop_shell = rop.RemoteOperations(user_host="localhost", use_shell=True)
+
+ def tearDown(self):
+ shutil.rmtree(self.temp_local_dir)
+ shutil.rmtree(self.temp_remote_dir)
+
+
+class RemoteOperationConnection(RemoteOperationsTestCase):
+ def runTest(self):
+
+ self.assertTrue(self.rop.access_established())
+ ret, buff = self.rop.access_info()
+ self.assertEqual(0, ret)
+
+ # Invalid host
+ remote_op = rop.RemoteOperations(user_host="badhost")
+ ret, buff = remote_op.access_info()
+ self.assertFalse(remote_op.access_established())
+ self.assertEqual(255, ret)
+ self.assertIsNotNone(buff)
+
+ # Invalid host with retries
+ remote_op = rop.RemoteOperations(user_host="badhost2", retries=3)
+ ret, buff = remote_op.access_info()
+ self.assertFalse(remote_op.access_established())
+ self.assertNotEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ # Invalid host with retries & retry_sleep
+ remote_op = rop.RemoteOperations(user_host="badhost3", retries=3, retry_sleep=1)
+ ret, buff = remote_op.access_info()
+ self.assertFalse(remote_op.access_established())
+ self.assertNotEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ # Valid host with invalid ssh_options
+ ssh_options = "-o invalid"
+ remote_op = rop.RemoteOperations(user_host="localhost", ssh_options=ssh_options)
+ ret, buff = remote_op.access_info()
+ self.assertFalse(remote_op.access_established())
+ self.assertNotEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ # Valid host with valid ssh_options
+ ssh_options = "-v -o ConnectTimeout=10 -o ConnectionAttempts=10"
+ remote_op = rop.RemoteOperations(user_host="localhost", ssh_options=ssh_options)
+ ret, buff = remote_op.access_info()
+ self.assertTrue(remote_op.access_established())
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+
+class RemoteOperationShell(RemoteOperationsTestCase):
+ def runTest(self):
+
+ # Shell connect
+ ret, buff = self.rop.shell("uname")
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ ret, buff = self.rop_shell.shell("uname")
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ ret, buff = self.rop.operation("shell", "uname")
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ # Invalid command
+ ret, buff = self.rop.shell("invalid_command")
+ self.assertNotEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ # Multiple commands
+ ret, buff = self.rop.shell("date; whoami; ls")
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ ret, buff = self.rop_shell.shell("date; whoami; ls")
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ # Command with single quotes
+ ret, buff = self.rop.shell("echo 'hello there' | grep 'hello'")
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ ret, buff = self.rop_shell.shell("echo 'hello there'| grep 'hello'")
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ # Command with escaped double quotes
+ ret, buff = self.rop.shell("echo \"hello there\" | grep \"hello\"")
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ ret, buff = self.rop_shell.shell("echo \"hello there\" | grep \"hello\"")
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ # Command with directory and pipe
+ ret, buff = self.rop.shell(
+ "touch {dir}/{file}; ls {dir} | grep {file}".format(
+ file="b",
+ dir=self.temp_local_dir))
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+ ret, buff = self.rop_shell.shell(
+ "touch {dir}/{file}; ls {dir} | grep {file}".format(
+ file="c",
+ dir=self.temp_local_dir))
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+
+
+class RemoteOperationCopy(RemoteOperationsTestCase):
+ def runTest(self):
+
+ # Copy to remote
+ l_temp_path = tempfile.mkstemp(dir=self.temp_local_dir)[1]
+ l_temp_file = os.path.basename(l_temp_path)
+ ret, buff = self.rop.copy_to(l_temp_path, self.temp_remote_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ r_temp_path = os.path.join(self.temp_remote_dir, l_temp_file)
+ self.assertTrue(os.path.isfile(r_temp_path))
+
+ l_temp_path = tempfile.mkstemp(dir=self.temp_local_dir)[1]
+ l_temp_file = os.path.basename(l_temp_path)
+ ret, buff = self.rop_shell.copy_to(l_temp_path, self.temp_remote_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ r_temp_path = os.path.join(self.temp_remote_dir, l_temp_file)
+ self.assertTrue(os.path.isfile(r_temp_path))
+
+ l_temp_path = tempfile.mkstemp(dir=self.temp_local_dir)[1]
+ l_temp_file = os.path.basename(l_temp_path)
+ ret, buff = self.rop.operation("copy_to", l_temp_path, self.temp_remote_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ self.assertTrue(os.path.isfile(r_temp_path))
+
+ # Copy multiple files to remote
+ num_files = 3
+ l_temp_files = []
+ for i in range(num_files):
+ l_temp_path = tempfile.mkstemp(dir=self.temp_local_dir)[1]
+ l_temp_file = os.path.basename(l_temp_path)
+ l_temp_files.append(l_temp_path)
+ ret, buff = self.rop.copy_to(" ".join(l_temp_files), self.temp_remote_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ for i in range(num_files):
+ r_temp_path = os.path.join(self.temp_remote_dir, os.path.basename(l_temp_files[i]))
+ self.assertTrue(os.path.isfile(r_temp_path))
+
+ num_files = 3
+ l_temp_files = []
+ for i in range(num_files):
+ l_temp_path = tempfile.mkstemp(dir=self.temp_local_dir)[1]
+ l_temp_file = os.path.basename(l_temp_path)
+ l_temp_files.append(l_temp_path)
+ ret, buff = self.rop_shell.copy_to(" ".join(l_temp_files), self.temp_remote_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ for i in range(num_files):
+ r_temp_path = os.path.join(self.temp_remote_dir, os.path.basename(l_temp_files[i]))
+ self.assertTrue(os.path.isfile(r_temp_path))
+
+ # Copy to remote without directory
+ l_temp_path = tempfile.mkstemp(dir=self.temp_local_dir)[1]
+ l_temp_file = os.path.basename(l_temp_path)
+ ret, buff = self.rop.copy_to(l_temp_path)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ r_temp_path = os.path.join(os.environ["HOME"], l_temp_file)
+ self.assertTrue(os.path.isfile(r_temp_path))
+ os.remove(r_temp_path)
+
+ l_temp_path = tempfile.mkstemp(dir=self.temp_local_dir)[1]
+ l_temp_file = os.path.basename(l_temp_path)
+ ret, buff = self.rop_shell.copy_to(l_temp_path)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ r_temp_path = os.path.join(os.environ["HOME"], l_temp_file)
+ self.assertTrue(os.path.isfile(r_temp_path))
+ os.remove(r_temp_path)
+
+ # Copy to remote with space in file name, note it must be quoted.
+ l_temp_path = tempfile.mkstemp(dir=self.temp_local_dir, prefix="filename with space")[1]
+ l_temp_file = os.path.basename(l_temp_path)
+ ret, buff = self.rop.copy_to("'{}'".format(l_temp_path))
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ r_temp_path = os.path.join(os.environ["HOME"], l_temp_file)
+ self.assertTrue(os.path.isfile(r_temp_path))
+ os.remove(r_temp_path)
+
+ l_temp_path = tempfile.mkstemp(dir=self.temp_local_dir, prefix="filename with space")[1]
+ l_temp_file = os.path.basename(l_temp_path)
+ ret, buff = self.rop_shell.copy_to("'{}'".format(l_temp_path))
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ r_temp_path = os.path.join(os.environ["HOME"], l_temp_file)
+ self.assertTrue(os.path.isfile(r_temp_path))
+ os.remove(r_temp_path)
+
+ # Copy from remote
+ r_temp_path = tempfile.mkstemp(dir=self.temp_remote_dir)[1]
+ r_temp_file = os.path.basename(r_temp_path)
+ ret, buff = self.rop.copy_from(r_temp_path, self.temp_local_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ l_temp_path = os.path.join(self.temp_local_dir, r_temp_file)
+ self.assertTrue(os.path.isfile(l_temp_path))
+
+ r_temp_path = tempfile.mkstemp(dir=self.temp_remote_dir)[1]
+ r_temp_file = os.path.basename(r_temp_path)
+ ret, buff = self.rop_shell.copy_from(r_temp_path, self.temp_local_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ l_temp_path = os.path.join(self.temp_local_dir, r_temp_file)
+ self.assertTrue(os.path.isfile(l_temp_path))
+
+ # Copy from remote without directory
+ r_temp_path = tempfile.mkstemp(dir=self.temp_remote_dir)[1]
+ r_temp_file = os.path.basename(r_temp_path)
+ ret, buff = self.rop.copy_from(r_temp_path)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ self.assertTrue(os.path.isfile(r_temp_file))
+ os.remove(r_temp_file)
+
+ r_temp_path = tempfile.mkstemp(dir=self.temp_remote_dir)[1]
+ r_temp_file = os.path.basename(r_temp_path)
+ ret, buff = self.rop_shell.copy_from(r_temp_path)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ self.assertTrue(os.path.isfile(r_temp_file))
+ os.remove(r_temp_file)
+
+ # Copy from remote with space in file name, note it must be quoted.
+ r_temp_path = tempfile.mkstemp(dir=self.temp_remote_dir, prefix="filename with space")[1]
+ r_temp_file = os.path.basename(r_temp_path)
+ ret, buff = self.rop.copy_from("'{}'".format(r_temp_path))
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ self.assertTrue(os.path.isfile(r_temp_file))
+ os.remove(r_temp_file)
+
+ # Copy multiple files from remote
+ num_files = 3
+ r_temp_files = []
+ for i in range(num_files):
+ r_temp_path = tempfile.mkstemp(dir=self.temp_remote_dir)[1]
+ r_temp_file = os.path.basename(r_temp_path)
+ r_temp_files.append(r_temp_path)
+ ret, buff = self.rop.copy_from(" ".join(r_temp_files), self.temp_local_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ for i in range(num_files):
+ basefile_name = os.path.basename(l_temp_files[i])
+ l_temp_path = os.path.join(self.temp_local_dir, basefile_name)
+ self.assertTrue(os.path.isfile(l_temp_path))
+
+ num_files = 3
+ r_temp_files = []
+ for i in range(num_files):
+ r_temp_path = tempfile.mkstemp(dir=self.temp_remote_dir)[1]
+ r_temp_file = os.path.basename(r_temp_path)
+ r_temp_files.append(r_temp_path)
+ ret, buff = self.rop_shell.copy_from(" ".join(r_temp_files), self.temp_local_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ for i in range(num_files):
+ basefile_name = os.path.basename(l_temp_files[i])
+ l_temp_path = os.path.join(self.temp_local_dir, basefile_name)
+ self.assertTrue(os.path.isfile(l_temp_path))
+
+ # Copy files from remote with wilcard
+ num_files = 3
+ r_temp_files = []
+ for i in range(num_files):
+ r_temp_path = tempfile.mkstemp(dir=self.temp_remote_dir, prefix="wild1")[1]
+ r_temp_file = os.path.basename(r_temp_path)
+ r_temp_files.append(r_temp_path)
+ r_temp_path = os.path.join(self.temp_remote_dir, "wild1*")
+ ret, buff = self.rop.copy_from(r_temp_path, self.temp_local_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ for i in range(num_files):
+ l_temp_path = os.path.join(self.temp_local_dir, os.path.basename(r_temp_files[i]))
+ self.assertTrue(os.path.isfile(l_temp_path))
+
+ num_files = 3
+ r_temp_files = []
+ for i in range(num_files):
+ r_temp_path = tempfile.mkstemp(dir=self.temp_remote_dir, prefix="wild2")[1]
+ r_temp_file = os.path.basename(r_temp_path)
+ r_temp_files.append(r_temp_path)
+ r_temp_path = os.path.join(self.temp_remote_dir, "wild2*")
+ ret, buff = self.rop_shell.copy_from(r_temp_path, self.temp_local_dir)
+ self.assertEqual(0, ret)
+ self.assertIsNotNone(buff)
+ for i in range(num_files):
+ l_temp_path = os.path.join(self.temp_local_dir, os.path.basename(r_temp_files[i]))
+ self.assertTrue(os.path.isfile(l_temp_path))
+
+ # Local directory does not exist.
+ self.assertRaises(ValueError, lambda: self.rop_shell.copy_from(r_temp_path, "bad_dir"))
+
+
+class RemoteOperation(RemoteOperationsTestCase):
+ def runTest(self):
+
+ # Invalid operation
+ self.assertRaises(ValueError, lambda: self.rop.operation("invalid", None))
+
+
+if __name__ == "__main__":
+ unittest.main()