Source code for virttest.remote

"""
Functions and classes used for logging into guests and transferring files.
"""
import logging
import time
import re
import os
import shutil
import tempfile
import aexpect
import utils_misc
import rss_client

from remote_commander import remote_master
from remote_commander import messenger

from autotest.client.shared import error
from autotest.client import utils
import data_dir


[docs]class LoginError(Exception): def __init__(self, msg, output): Exception.__init__(self, msg, output) self.msg = msg self.output = output def __str__(self): return "%s (output: %r)" % (self.msg, self.output)
[docs]class LoginAuthenticationError(LoginError): pass
[docs]class LoginTimeoutError(LoginError): def __init__(self, output): LoginError.__init__(self, "Login timeout expired", output)
[docs]class LoginProcessTerminatedError(LoginError): def __init__(self, status, output): LoginError.__init__(self, None, output) self.status = status def __str__(self): return ("Client process terminated (status: %s, output: %r)" % (self.status, self.output))
[docs]class LoginBadClientError(LoginError): def __init__(self, client): LoginError.__init__(self, None, None) self.client = client def __str__(self): return "Unknown remote shell client: %r" % self.client
[docs]class SCPError(Exception): def __init__(self, msg, output): Exception.__init__(self, msg, output) self.msg = msg self.output = output def __str__(self): return "%s (output: %r)" % (self.msg, self.output)
[docs]class SCPAuthenticationError(SCPError): pass
[docs]class SCPAuthenticationTimeoutError(SCPAuthenticationError): def __init__(self, output): SCPAuthenticationError.__init__(self, "Authentication timeout expired", output)
[docs]class SCPTransferTimeoutError(SCPError): def __init__(self, output): SCPError.__init__(self, "Transfer timeout expired", output)
[docs]class SCPTransferFailedError(SCPError): def __init__(self, status, output): SCPError.__init__(self, None, output) self.status = status def __str__(self): return ("SCP transfer failed (status: %s, output: %r)" % (self.status, self.output))
[docs]def handle_prompts(session, username, password, prompt, timeout=10, debug=False): """ Connect to a remote host (guest) using SSH or Telnet or else. Wait for questions and provide answers. If timeout expires while waiting for output from the child (e.g. a password prompt or a shell prompt) -- fail. :param session: An Expect or ShellSession instance to operate on :param username: The username to send in reply to a login prompt :param password: The password to send in reply to a password prompt :param prompt: The shell prompt that indicates a successful login :param timeout: The maximal time duration (in seconds) to wait for each step of the login procedure (i.e. the "Are you sure" prompt, the password prompt, the shell prompt, etc) :raise LoginTimeoutError: If timeout expires :raise LoginAuthenticationError: If authentication fails :raise LoginProcessTerminatedError: If the client terminates during login :raise LoginError: If some other error occurs :return: If connect succeed return the output text to script for further debug. """ password_prompt_count = 0 login_prompt_count = 0 output = "" while True: try: match, text = session.read_until_last_line_matches( [r"[Aa]re you sure", r"[Pp]assword:\s*", r"\(or (press|type) Control-D to continue\):\s*$", # Prompt of rescue mode for Red Hat. r"[Gg]ive.*[Ll]ogin:\s*$", # Prompt of rescue mode for SUSE. r"(?<![Ll]ast )[Ll]ogin:\s*$", # Don't match "Last Login:" r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", r"[Pp]lease wait", r"[Ww]arning", r"[Ee]nter.*username", r"[Ee]nter.*password", r"[Cc]onnection timed out", prompt], timeout=timeout, internal_timeout=0.5) output += text if match == 0: # "Are you sure you want to continue connecting" if debug: logging.debug("Got 'Are you sure...', sending 'yes'") session.sendline("yes") continue elif match in [1, 2, 3, 10]: # "password:" if password_prompt_count == 0: if debug: logging.debug("Got password prompt, sending '%s'", password) session.sendline(password) password_prompt_count += 1 continue else: raise LoginAuthenticationError("Got password prompt twice", text) elif match == 4 or match == 9: # "login:" if login_prompt_count == 0 and password_prompt_count == 0: if debug: logging.debug("Got username prompt; sending '%s'", username) session.sendline(username) login_prompt_count += 1 continue else: if login_prompt_count > 0: msg = "Got username prompt twice" else: msg = "Got username prompt after password prompt" raise LoginAuthenticationError(msg, text) elif match == 5: # "Connection closed" raise LoginError("Client said 'connection closed'", text) elif match == 6: # "Connection refused" raise LoginError("Client said 'connection refused'", text) elif match == 11: # Connection timeout raise LoginError("Client said 'connection timeout'", text) elif match == 7: # "Please wait" if debug: logging.debug("Got 'Please wait'") timeout = 30 continue elif match == 8: # "Warning added RSA" if debug: logging.debug("Got 'Warning added RSA to known host list") continue elif match == 12: # prompt if debug: logging.debug("Got shell prompt -- logged in") break except aexpect.ExpectTimeoutError, e: raise LoginTimeoutError(e.output) except aexpect.ExpectProcessTerminatedError, e: raise LoginProcessTerminatedError(e.status, e.output) return output
[docs]def remote_login(client, host, port, username, password, prompt, linesep="\n", log_filename=None, timeout=10, interface=None, status_test_command="echo $?"): """ Log into a remote host (guest) using SSH/Telnet/Netcat. :param client: The client to use ('ssh', 'telnet' or 'nc') :param host: Hostname or IP address :param port: Port to connect to :param username: Username (if required) :param password: Password (if required) :param prompt: Shell prompt (regular expression) :param linesep: The line separator to use when sending lines (e.g. '\\n' or '\\r\\n') :param log_filename: If specified, log all output to this file :param timeout: The maximal time duration (in seconds) to wait for each step of the login procedure (i.e. the "Are you sure" prompt or the password prompt) :interface: The interface the neighbours attach to (only use when using ipv6 linklocal address.) :param status_test_command: Command to be used for getting the last exit status of commands run inside the shell (used by cmd_status_output() and friends). :raise LoginError: If using ipv6 linklocal but not assign a interface that the neighbour attache :raise LoginBadClientError: If an unknown client is requested :raise: Whatever handle_prompts() raises :return: A ShellSession object. """ if host and host.lower().startswith("fe80"): if not interface: raise LoginError("When using ipv6 linklocal an interface must " "be assigned") host = "%s%%%s" % (host, interface) if client == "ssh": cmd = ("ssh -o UserKnownHostsFile=/dev/null " "-o StrictHostKeyChecking=no " "-o PreferredAuthentications=password -p %s %s@%s" % (port, username, host)) elif client == "telnet": cmd = "telnet -l %s %s %s" % (username, host, port) elif client == "nc": cmd = "nc %s %s" % (host, port) else: raise LoginBadClientError(client) logging.debug("Login command: '%s'", cmd) session = aexpect.ShellSession(cmd, linesep=linesep, prompt=prompt, status_test_command=status_test_command) try: handle_prompts(session, username, password, prompt, timeout) except Exception: session.close() raise if log_filename: session.set_output_func(utils_misc.log_line) session.set_output_params((log_filename,)) session.set_log_file(log_filename) return session
[docs]class AexpectIOWrapperOut(messenger.StdIOWrapperOutBase64): """ Basic implementation of IOWrapper for stdout """
[docs] def close(self): self._obj.close()
[docs] def fileno(self): return os.open(self._obj, os.O_RDWR)
[docs] def write(self, data): self._obj.send(data)
[docs]def remote_commander(client, host, port, username, password, prompt, linesep="\n", log_filename=None, timeout=10, path=None): """ Log into a remote host (guest) using SSH/Telnet/Netcat. :param client: The client to use ('ssh', 'telnet' or 'nc') :param host: Hostname or IP address :param port: Port to connect to :param username: Username (if required) :param password: Password (if required) :param prompt: Shell prompt (regular expression) :param linesep: The line separator to use when sending lines (e.g. '\\n' or '\\r\\n') :param log_filename: If specified, log all output to this file :param timeout: The maximal time duration (in seconds) to wait for each step of the login procedure (i.e. the "Are you sure" prompt or the password prompt) :param path: The path to place where remote_runner.py is placed. :raise LoginBadClientError: If an unknown client is requested :raise: Whatever handle_prompts() raises :return: A ShellSession object. """ if path is None: path = "/tmp" if client == "ssh": cmd = ("ssh -o UserKnownHostsFile=/dev/null " "-o PreferredAuthentications=password " "-p %s %s@%s %s agent_base64" % (port, username, host, os.path.join(path, "remote_runner.py"))) elif client == "telnet": cmd = "telnet -l %s %s %s" % (username, host, port) elif client == "nc": cmd = "nc %s %s" % (host, port) else: raise LoginBadClientError(client) logging.debug("Login command: '%s'", cmd) session = aexpect.Expect(cmd, linesep=linesep) try: handle_prompts(session, username, password, prompt, timeout) except Exception: session.close() raise if log_filename: session.set_output_func(utils_misc.log_line) session.set_output_params((log_filename,)) session.set_log_file(log_filename) session.send_ctrl("raw") # Wrap io interfaces. inw = messenger.StdIOWrapperInBase64(session._get_fd("tail")) outw = AexpectIOWrapperOut(session) # Create commander cmd = remote_master.CommanderMaster(inw, outw, False) return cmd
[docs]def wait_for_login(client, host, port, username, password, prompt, linesep="\n", log_filename=None, timeout=240, internal_timeout=10, interface=None): """ Make multiple attempts to log into a guest until one succeeds or timeouts. :param timeout: Total time duration to wait for a successful login :param internal_timeout: The maximum time duration (in seconds) to wait for each step of the login procedure (e.g. the "Are you sure" prompt or the password prompt) :interface: The interface the neighbours attach to (only use when using ipv6 linklocal address.) :see: remote_login() :raise: Whatever remote_login() raises :return: A ShellSession object. """ logging.debug("Attempting to log into %s:%s using %s (timeout %ds)", host, port, client, timeout) end_time = time.time() + timeout while time.time() < end_time: try: return remote_login(client, host, port, username, password, prompt, linesep, log_filename, internal_timeout, interface) except LoginError, e: logging.debug(e) time.sleep(2) # Timeout expired; try one more time but don't catch exceptions return remote_login(client, host, port, username, password, prompt, linesep, log_filename, internal_timeout, interface)
def _remote_scp(session, password_list, transfer_timeout=600, login_timeout=20): """ Transfer files using SCP, given a command line. Transfer file(s) to a remote host (guest) using SCP. Wait for questions and provide answers. If login_timeout expires while waiting for output from the child (e.g. a password prompt), fail. If transfer_timeout expires while waiting for the transfer to complete, fail. :param session: An Expect or ShellSession instance to operate on :param password_list: Password list to send in reply to the password prompt :param transfer_timeout: The time duration (in seconds) to wait for the transfer to complete. :param login_timeout: The maximal time duration (in seconds) to wait for each step of the login procedure (i.e. the "Are you sure" prompt or the password prompt) :raise SCPAuthenticationError: If authentication fails :raise SCPTransferTimeoutError: If the transfer fails to complete in time :raise SCPTransferFailedError: If the process terminates with a nonzero exit code :raise SCPError: If some other error occurs """ password_prompt_count = 0 timeout = login_timeout authentication_done = False scp_type = len(password_list) while True: try: match, text = session.read_until_last_line_matches( [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"], timeout=timeout, internal_timeout=0.5) if match == 0: # "Are you sure you want to continue connecting" logging.debug("Got 'Are you sure...', sending 'yes'") session.sendline("yes") continue elif match == 1: # "password:" if password_prompt_count == 0: logging.debug("Got password prompt, sending '%s'" % password_list[password_prompt_count]) session.sendline(password_list[password_prompt_count]) password_prompt_count += 1 timeout = transfer_timeout if scp_type == 1: authentication_done = True continue elif password_prompt_count == 1 and scp_type == 2: logging.debug("Got password prompt, sending '%s'" % password_list[password_prompt_count]) session.sendline(password_list[password_prompt_count]) password_prompt_count += 1 timeout = transfer_timeout authentication_done = True continue else: raise SCPAuthenticationError("Got password prompt twice", text) elif match == 2: # "lost connection" raise SCPError("SCP client said 'lost connection'", text) except aexpect.ExpectTimeoutError, e: if authentication_done: raise SCPTransferTimeoutError(e.output) else: raise SCPAuthenticationTimeoutError(e.output) except aexpect.ExpectProcessTerminatedError, e: if e.status == 0: logging.debug("SCP process terminated with status 0") break else: raise SCPTransferFailedError(e.status, e.output)
[docs]def remote_scp(command, password_list, log_filename=None, transfer_timeout=600, login_timeout=20): """ Transfer files using SCP, given a command line. :param command: The command to execute (e.g. "scp -r foobar root@localhost:/tmp/"). :param password_list: Password list to send in reply to a password prompt. :param log_filename: If specified, log all output to this file :param transfer_timeout: The time duration (in seconds) to wait for the transfer to complete. :param login_timeout: The maximal time duration (in seconds) to wait for each step of the login procedure (i.e. the "Are you sure" prompt or the password prompt) :raise: Whatever _remote_scp() raises """ logging.debug("Trying to SCP with command '%s', timeout %ss", command, transfer_timeout) if log_filename: output_func = utils_misc.log_line output_params = (log_filename,) else: output_func = None output_params = () session = aexpect.Expect(command, output_func=output_func, output_params=output_params) try: _remote_scp(session, password_list, transfer_timeout, login_timeout) finally: session.close()
[docs]def scp_to_remote(host, port, username, password, local_path, remote_path, limit="", log_filename=None, timeout=600, interface=None): """ Copy files to a remote host (guest) through scp. :param host: Hostname or IP address :param username: Username (if required) :param password: Password (if required) :param local_path: Path on the local machine where we are copying from :param remote_path: Path on the remote machine where we are copying to :param limit: Speed limit of file transfer. :param log_filename: If specified, log all output to this file :param timeout: The time duration (in seconds) to wait for the transfer to complete. :interface: The interface the neighbours attach to (only use when using ipv6 linklocal address.) :raise: Whatever remote_scp() raises """ if (limit): limit = "-l %s" % (limit) if host and host.lower().startswith("fe80"): if not interface: raise SCPError("When using ipv6 linklocal address must assign", "the interface the neighbour attache") host = "%s%%%s" % (host, interface) command = ("scp -v -o UserKnownHostsFile=/dev/null " "-o StrictHostKeyChecking=no " "-o PreferredAuthentications=password -r %s " "-P %s %s %s@\[%s\]:%s" % (limit, port, local_path, username, host, remote_path)) password_list = [] password_list.append(password) return remote_scp(command, password_list, log_filename, timeout)
[docs]def scp_from_remote(host, port, username, password, remote_path, local_path, limit="", log_filename=None, timeout=600, interface=None): """ Copy files from a remote host (guest). :param host: Hostname or IP address :param username: Username (if required) :param password: Password (if required) :param local_path: Path on the local machine where we are copying from :param remote_path: Path on the remote machine where we are copying to :param limit: Speed limit of file transfer. :param log_filename: If specified, log all output to this file :param timeout: The time duration (in seconds) to wait for the transfer to complete. :interface: The interface the neighbours attach to (only use when using ipv6 linklocal address.) :raise: Whatever remote_scp() raises """ if (limit): limit = "-l %s" % (limit) if host and host.lower().startswith("fe80"): if not interface: raise SCPError("When using ipv6 linklocal address must assign, ", "the interface the neighbour attache") host = "%s%%%s" % (host, interface) command = ("scp -v -o UserKnownHostsFile=/dev/null " "-o StrictHostKeyChecking=no " "-o PreferredAuthentications=password -r %s " "-P %s %s@\[%s\]:%s %s" % (limit, port, username, host, remote_path, local_path)) password_list = [] password_list.append(password) remote_scp(command, password_list, log_filename, timeout)
[docs]def scp_between_remotes(src, dst, port, s_passwd, d_passwd, s_name, d_name, s_path, d_path, limit="", log_filename=None, timeout=600, src_inter=None, dst_inter=None): """ Copy files from a remote host (guest) to another remote host (guest). :param src/dst: Hostname or IP address of src and dst :param s_name/d_name: Username (if required) :param s_passwd/d_passwd: Password (if required) :param s_path/d_path: Path on the remote machine where we are copying from/to :param limit: Speed limit of file transfer. :param log_filename: If specified, log all output to this file :param timeout: The time duration (in seconds) to wait for the transfer to complete. :src_inter: The interface on local that the src neighbour attache :dst_inter: The interface on the src that the dst neighbour attache :return: True on success and False on failure. """ if (limit): limit = "-l %s" % (limit) if src and src.lower().startswith("fe80"): if not src_inter: raise SCPError("When using ipv6 linklocal address must assign ", "the interface the neighbour attache") src = "%s%%%s" % (src, src_inter) if dst and dst.lower().startswith("fe80"): if not dst_inter: raise SCPError("When using ipv6 linklocal address must assign ", "the interface the neighbour attache") dst = "%s%%%s" % (dst, dst_inter) command = ("scp -v -o UserKnownHostsFile=/dev/null " "-o StrictHostKeyChecking=no " "-o PreferredAuthentications=password -r %s -P %s" " %s@\[%s\]:%s %s@\[%s\]:%s" % (limit, port, s_name, src, s_path, d_name, dst, d_path)) password_list = [] password_list.append(s_passwd) password_list.append(d_passwd) return remote_scp(command, password_list, log_filename, timeout)
[docs]def nc_copy_between_remotes(src, dst, s_port, s_passwd, d_passwd, s_name, d_name, s_path, d_path, c_type="ssh", c_prompt="\n", d_port="8888", d_protocol="udp", timeout=10, check_sum=True): """ Copy files from guest to guest using netcat. This method only supports linux guest OS. :param src/dst: Hostname or IP address of src and dst :param s_name/d_name: Username (if required) :param s_passwd/d_passwd: Password (if required) :param s_path/d_path: Path on the remote machine where we are copying :param c_type: Login method to remote host(guest). :param c_prompt: command line prompt of remote host(guest) :param d_port: the port data transfer :param d_protocol: nc protocol use (tcp or udp) :param timeout: If a connection and stdin are idle for more than timeout seconds, then the connection is silently closed. :return: True on success and False on failure. """ s_session = remote_login(c_type, src, s_port, s_name, s_passwd, c_prompt) d_session = remote_login(c_type, dst, s_port, d_name, d_passwd, c_prompt) s_session.cmd("iptables -I INPUT -p %s -j ACCEPT" % d_protocol) d_session.cmd("iptables -I OUTPUT -p %s -j ACCEPT" % d_protocol) logging.info("Transfer data using netcat from %s to %s" % (src, dst)) cmd = "nc" if d_protocol == "udp": cmd += " -u" cmd += " -w %s" % timeout s_session.sendline("%s -l %s < %s" % (cmd, d_port, s_path)) d_session.sendline("echo a | %s %s %s > %s" % (cmd, src, d_port, d_path)) if check_sum: if (s_session.cmd("md5sum %s" % s_path).split()[0] != d_session.cmd("md5sum %s" % d_path).split()[0]): return False return True
[docs]def udp_copy_between_remotes(src, dst, s_port, s_passwd, d_passwd, s_name, d_name, s_path, d_path, c_type="ssh", c_prompt="\n", d_port="9000", timeout=600): """ Copy files from guest to guest using udp. :param src/dst: Hostname or IP address of src and dst :param s_name/d_name: Username (if required) :param s_passwd/d_passwd: Password (if required) :param s_path/d_path: Path on the remote machine where we are copying :param c_type: Login method to remote host(guest). :param c_prompt: command line prompt of remote host(guest) :param d_port: the port data transfer :param timeout: data transfer timeout """ s_session = remote_login(c_type, src, s_port, s_name, s_passwd, c_prompt) d_session = remote_login(c_type, dst, s_port, d_name, d_passwd, c_prompt) def get_abs_path(session, filename, extension): """ return file path drive+path """ cmd_tmp = "wmic datafile where \"Filename='%s' and " cmd_tmp += "extension='%s'\" get drive^,path" cmd = cmd_tmp % (filename, extension) info = session.cmd_output(cmd, timeout=360).strip() drive_path = re.search(r'(\w):\s+(\S+)', info, re.M) if not drive_path: raise error.TestError("Not found file %s.%s in your guest" % (filename, extension)) return ":".join(drive_path.groups()) def get_file_md5(session, file_path): """ Get files md5sums """ if c_type == "ssh": md5_cmd = "md5sum %s" % file_path md5_reg = r"(\w+)\s+%s.*" % file_path else: drive_path = get_abs_path(session, "md5sums", "exe") filename = file_path.split("\\")[-1] md5_reg = r"%s\s+(\w+)" % filename md5_cmd = '%smd5sums.exe %s | find "%s"' % (drive_path, file_path, filename) o = session.cmd_output(md5_cmd) file_md5 = re.findall(md5_reg, o) if not o: raise error.TestError("Get file %s md5sum error" % file_path) return file_md5 def server_alive(session): if c_type == "ssh": check_cmd = "ps aux" else: check_cmd = "tasklist" o = session.cmd_output(check_cmd) if not o: raise error.TestError("Can not get the server status") if "sendfile" in o.lower(): return True return False def start_server(session): if c_type == "ssh": start_cmd = "sendfile %s &" % d_port else: drive_path = get_abs_path(session, "sendfile", "exe") start_cmd = "start /b %ssendfile.exe %s" % (drive_path, d_port) session.cmd_output_safe(start_cmd) if not server_alive(session): raise error.TestError("Start udt server failed") def start_client(session): if c_type == "ssh": client_cmd = "recvfile %s %s %s %s" % (src, d_port, s_path, d_path) else: drive_path = get_abs_path(session, "recvfile", "exe") client_cmd_tmp = "%srecvfile.exe %s %s %s %s" client_cmd = client_cmd_tmp % (drive_path, src, d_port, s_path.split("\\")[-1], d_path.split("\\")[-1]) session.cmd_output_safe(client_cmd, timeout) def stop_server(session): if c_type == "ssh": stop_cmd = "killall sendfile" else: stop_cmd = "taskkill /F /IM sendfile.exe" if server_alive(session): session.cmd_output_safe(stop_cmd) try: src_md5 = get_file_md5(s_session, s_path) if not server_alive(s_session): start_server(s_session) start_client(d_session) dst_md5 = get_file_md5(d_session, d_path) if src_md5 != dst_md5: err_msg = "Files md5sum mismatch, file %s md5sum is '%s', " err_msg = "but the file %s md5sum is %s" raise error.TestError(err_msg % (s_path, src_md5, d_path, dst_md5)) finally: stop_server(s_session) s_session.close() d_session.close()
[docs]def copy_files_to(address, client, username, password, port, local_path, remote_path, limit="", log_filename=None, verbose=False, timeout=600, interface=None): """ Copy files to a remote host (guest) using the selected client. :param client: Type of transfer client :param username: Username (if required) :param password: Password (if requried) :param local_path: Path on the local machine where we are copying from :param remote_path: Path on the remote machine where we are copying to :param address: Address of remote host(guest) :param limit: Speed limit of file transfer. :param log_filename: If specified, log all output to this file (SCP only) :param verbose: If True, log some stats using logging.debug (RSS only) :param timeout: The time duration (in seconds) to wait for the transfer to complete. :interface: The interface the neighbours attach to (only use when using ipv6 linklocal address.) :raise: Whatever remote_scp() raises """ if client == "scp": scp_to_remote(address, port, username, password, local_path, remote_path, limit, log_filename, timeout, interface=interface) elif client == "rss": log_func = None if verbose: log_func = logging.debug if interface: address = "%s%%%s" % (address, interface) c = rss_client.FileUploadClient(address, port, log_func) c.upload(local_path, remote_path, timeout) c.close() else: raise error.TestError("No such file copy client: '%s', valid values" "are scp and rss" % client)
[docs]def copy_files_from(address, client, username, password, port, remote_path, local_path, limit="", log_filename=None, verbose=False, timeout=600, interface=None): """ Copy files from a remote host (guest) using the selected client. :param client: Type of transfer client :param username: Username (if required) :param password: Password (if requried) :param remote_path: Path on the remote machine where we are copying from :param local_path: Path on the local machine where we are copying to :param address: Address of remote host(guest) :param limit: Speed limit of file transfer. :param log_filename: If specified, log all output to this file (SCP only) :param verbose: If True, log some stats using ``logging.debug`` (RSS only) :param timeout: The time duration (in seconds) to wait for the transfer to complete. :interface: The interface the neighbours attach to (only use when using ipv6 linklocal address.) :raise: Whatever ``remote_scp()`` raises """ if client == "scp": scp_from_remote(address, port, username, password, remote_path, local_path, limit, log_filename, timeout, interface=interface) elif client == "rss": log_func = None if verbose: log_func = logging.debug if interface: address = "%s%%%s" % (address, interface) c = rss_client.FileDownloadClient(address, port, log_func) c.download(remote_path, local_path, timeout) c.close() else: raise error.TestError("No such file copy client: '%s', valid values" "are scp and rss" % client)
[docs]class Remote_Package(object): def __init__(self, address, client, username, password, port, remote_path): """ Initialization of Remote Package class. :param address: Address of remote host(guest) :param client: The client to use ('ssh', 'telnet' or 'nc') :param username: Username (if required) :param password: Password (if requried) :param port: Port to connect to :param remote_path: Rmote package path """ self.address = address self.client = client self.port = port self.username = username self.password = password self.remote_path = remote_path if self.client == "nc": self.cp_client = "rss" self.cp_port = 10023 elif self.client == "ssh": self.cp_client = "scp" self.cp_port = 22 else: raise LoginBadClientError(client)
[docs] def pull_file(self, local_path, timeout=600): """ Copy file from remote to local. """ logging.debug("Pull remote: '%s' to local: '%s'." % (self.remote_path, local_path)) copy_files_from(self.address, self.cp_client, self.username, self.password, self.cp_port, self.remote_path, local_path, timeout=timeout)
[docs] def push_file(self, local_path, timeout=600): """ Copy file from local to remote. """ logging.debug("Push local: '%s' to remote: '%s'." % (local_path, self.remote_path)) copy_files_to(self.address, self.cp_client, self.username, self.password, self.cp_port, local_path, self.remote_path, timeout=timeout)
[docs]class RemoteFile(object): """ Class to handle the operations of file on remote host or guest. """ def __init__(self, address, client, username, password, port, remote_path, limit="", log_filename=None, verbose=False, timeout=600): """ Initialization of RemoteFile class. :param address: Address of remote host(guest) :param client: Type of transfer client :param username: Username (if required) :param password: Password (if requried) :param remote_path: Path of file which we want to edit on remote. :param limit: Speed limit of file transfer. :param log_filename: If specified, log all output to this file(SCP only) :param verbose: If True, log some stats using logging.debug (RSS only) :param timeout: The time duration (in seconds) to wait for the transfer tocomplete. """ self.address = address self.client = client self.username = username self.password = password self.port = port self.remote_path = remote_path self.limit = limit self.log_filename = log_filename self.verbose = verbose self.timeout = timeout # Get a local_path and all actions is taken on it. filename = os.path.basename(self.remote_path) # Get a local_path. tmp_dir = data_dir.get_tmp_dir() local_file = tempfile.NamedTemporaryFile(prefix=("%s_" % filename), dir=tmp_dir) self.local_path = local_file.name local_file.close() # Get a backup_path. backup_file = tempfile.NamedTemporaryFile(prefix=("%s_" % filename), dir=tmp_dir) self.backup_path = backup_file.name backup_file.close() # Get file from remote. try: self._pull_file() except SCPTransferFailedError: # Remote file doesn't exist, create empty file on local self._write_local([]) # Save a backup. shutil.copy(self.local_path, self.backup_path) def __del__(self): """ Called when the instance is about to be destroyed. """ self._reset_file() if os.path.exists(self.backup_path): os.remove(self.backup_path) if os.path.exists(self.local_path): os.remove(self.local_path) def _pull_file(self): """ Copy file from remote to local. """ if self.client == "test": shutil.copy(self.remote_path, self.local_path) else: copy_files_from(self.address, self.client, self.username, self.password, self.port, self.remote_path, self.local_path, self.limit, self.log_filename, self.verbose, self.timeout) def _push_file(self): """ Copy file from local to remote. """ if self.client == "test": shutil.copy(self.local_path, self.remote_path) else: copy_files_to(self.address, self.client, self.username, self.password, self.port, self.local_path, self.remote_path, self.limit, self.log_filename, self.verbose, self.timeout) def _reset_file(self): """ Copy backup from local to remote. """ if self.client == "test": shutil.copy(self.backup_path, self.remote_path) else: copy_files_to(self.address, self.client, self.username, self.password, self.port, self.backup_path, self.remote_path, self.limit, self.log_filename, self.verbose, self.timeout) def _read_local(self): """ Read file on local_path. :return: string list got from readlines(). """ local_file = open(self.local_path, "r") lines = local_file.readlines() local_file.close() return lines def _write_local(self, lines): """ Write file on local_path. Call writelines method of File. """ local_file = open(self.local_path, "w") local_file.writelines(lines) local_file.close()
[docs] def add(self, line_list): """ Append lines in line_list into file on remote. """ lines = self._read_local() for line in line_list: lines.append("\n%s" % line) self._write_local(lines) self._push_file()
[docs] def sub(self, pattern2repl_dict): """ Replace the string which match the pattern to the value contained in pattern2repl_dict. """ lines = self._read_local() for pattern, repl in pattern2repl_dict.items(): for index in range(len(lines)): line = lines[index] lines[index] = re.sub(pattern, repl, line) self._write_local(lines) self._push_file()
[docs] def truncate(self, length=0): """ Truncate the detail of remote file to assigned length Content before line 1 line 2 line 3 remote_file.truncate(length=1) Content after line 1 :param length: how many lines you want to keep """ lines = self._read_local() lines = lines[0: length] self._write_local(lines) self._push_file()
[docs] def remove(self, pattern_list): """ Remove the lines in remote file which matchs a pattern in pattern_list. """ lines = self._read_local() for pattern in pattern_list: for index in range(len(lines)): line = lines[index] if re.match(pattern, line): lines.remove(line) # Check this line is the last one or not. if (not line.endswith('\n') and (index > 0)): lines[index - 1] = lines[index - 1].rstrip("\n") self._write_local(lines) self._push_file()
[docs] def sub_else_add(self, pattern2repl_dict): """ Replace the string which match the pattern. If no match in the all lines, append the value to the end of file. """ lines = self._read_local() for pattern, repl in pattern2repl_dict.items(): no_line_match = True for index in range(len(lines)): line = lines[index] if re.match(pattern, line): no_line_match = False lines[index] = re.sub(pattern, repl, line) if no_line_match: lines.append("\n%s" % repl) self._write_local(lines) self._push_file()
[docs]class RemoteRunner(object): """ Class to provide a utils.run-like method to execute command on remote host or guest. Provide a similar interface with utils.run on local. """ def __init__(self, client="ssh", host=None, port="22", username="root", password=None, prompt=r"[\#\$]\s*$", linesep="\n", log_filename=None, timeout=240, internal_timeout=10, session=None): """ Initialization of RemoteRunner. Init a session login to remote host or guest. :param client: The client to use ('ssh', 'telnet' or 'nc') :param host: Hostname or IP address :param port: Port to connect to :param username: Username (if required) :param password: Password (if required) :param prompt: Shell prompt (regular expression) :param linesep: The line separator to use when sending lines (e.g. '\\n' or '\\r\\n') :param log_filename: If specified, log all output to this file :param timeout: Total time duration to wait for a successful login :param internal_timeout: The maximal time duration (in seconds) to wait for each step of the login procedure (e.g. the "Are you sure" prompt or the password prompt) :param session: An existing session :see: wait_for_login() :raise: Whatever wait_for_login() raises """ if session is None: if host is None: raise error.TestError("Neither host, nor session was defined!") self.session = wait_for_login(client, host, port, username, password, prompt, linesep, log_filename, timeout, internal_timeout) else: self.session = session # Init stdout pipe and stderr pipe. self.stdout_pipe = tempfile.mktemp() self.stderr_pipe = tempfile.mktemp()
[docs] def run(self, command, timeout=60, ignore_status=False): """ Method to provide a utils.run-like interface to execute command on remote host or guest. :param timeout: Total time duration to wait for command return. :param ignore_status: If ignore_status=True, do not raise an exception, no matter what the exit code of the command is. Else, raise CmdError if exit code of command is not zero. """ # Redirect the stdout and stderr to file, Deviding error message # from output, and taking off the color of output. To return the same # result with utils.run() function. command = "%s 1>%s 2>%s" % (command, self.stdout_pipe, self.stderr_pipe) status, _ = self.session.cmd_status_output(command, timeout=timeout) output = self.session.cmd_output("cat %s;rm -f %s" % (self.stdout_pipe, self.stdout_pipe)) errput = self.session.cmd_output("cat %s;rm -f %s" % (self.stderr_pipe, self.stderr_pipe)) cmd_result = utils.CmdResult(command=command, exit_status=status, stdout=output, stderr=errput) if (status and (not ignore_status)): raise error.CmdError(command, cmd_result) return cmd_result