| Index: client/tests/kvm/kvm_utils.py
|
| diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py
|
| index b849b37b2dd83a1a8670a4a5d71af82459d6a40e..44ebb88994c750c032c5ef6066319d3830ec10f1 100644
|
| --- a/client/tests/kvm/kvm_utils.py
|
| +++ b/client/tests/kvm/kvm_utils.py
|
| @@ -5,7 +5,7 @@ KVM test utility functions.
|
| """
|
|
|
| import time, string, random, socket, os, signal, re, logging, commands, cPickle
|
| -import fcntl, shelve, ConfigParser
|
| +import fcntl, shelve, ConfigParser, rss_file_transfer, threading, sys, UserDict
|
| from autotest_lib.client.bin import utils, os_dep
|
| from autotest_lib.client.common_lib import error, logging_config
|
| import kvm_subprocess
|
| @@ -27,74 +27,152 @@ def _unlock_file(f):
|
| f.close()
|
|
|
|
|
| -def dump_env(obj, filename):
|
| +def is_vm(obj):
|
| """
|
| - Dump KVM test environment to a file.
|
| + Tests whether a given object is a VM object.
|
|
|
| - @param filename: Path to a file where the environment will be dumped to.
|
| + @param obj: Python object.
|
| """
|
| - file = open(filename, "w")
|
| - cPickle.dump(obj, file)
|
| - file.close()
|
| + return obj.__class__.__name__ == "VM"
|
|
|
|
|
| -def load_env(filename, version):
|
| +class Env(UserDict.IterableUserDict):
|
| """
|
| - Load KVM test environment from an env file.
|
| - If the version recorded in the file is lower than version, return an empty
|
| - env. If some other error occurs during unpickling, return an empty env.
|
| -
|
| - @param filename: Path to an env file.
|
| + A dict-like object containing global objects used by tests.
|
| """
|
| - default = {"version": version}
|
| - try:
|
| - file = open(filename, "r")
|
| - env = cPickle.load(file)
|
| - file.close()
|
| - if env.get("version", 0) < version:
|
| - logging.warn("Incompatible env file found. Not using it.")
|
| - return default
|
| - return env
|
| - # Almost any exception can be raised during unpickling, so let's catch
|
| - # them all
|
| - except Exception, e:
|
| - logging.warn(e)
|
| - return default
|
| + def __init__(self, filename=None, version=0):
|
| + """
|
| + Create an empty Env object or load an existing one from a file.
|
|
|
| + If the version recorded in the file is lower than version, or if some
|
| + error occurs during unpickling, or if filename is not supplied,
|
| + create an empty Env object.
|
|
|
| -def get_sub_dict(dict, name):
|
| - """
|
| - Return a "sub-dict" corresponding to a specific object.
|
| + @param filename: Path to an env file.
|
| + @param version: Required env version (int).
|
| + """
|
| + UserDict.IterableUserDict.__init__(self)
|
| + empty = {"version": version}
|
| + if filename:
|
| + self._filename = filename
|
| + try:
|
| + f = open(filename, "r")
|
| + env = cPickle.load(f)
|
| + f.close()
|
| + if env.get("version", 0) >= version:
|
| + self.data = env
|
| + else:
|
| + logging.warn("Incompatible env file found. Not using it.")
|
| + self.data = empty
|
| + # Almost any exception can be raised during unpickling, so let's
|
| + # catch them all
|
| + except Exception, e:
|
| + logging.warn(e)
|
| + self.data = empty
|
| + else:
|
| + self.data = empty
|
|
|
| - Operate on a copy of dict: for each key that ends with the suffix
|
| - "_" + name, strip the suffix from the key, and set the value of
|
| - the stripped key to that of the key. Return the resulting dict.
|
|
|
| - @param name: Suffix of the key we want to set the value.
|
| - """
|
| - suffix = "_" + name
|
| - new_dict = dict.copy()
|
| - for key in dict.keys():
|
| - if key.endswith(suffix):
|
| - new_key = key.split(suffix)[0]
|
| - new_dict[new_key] = dict[key]
|
| - return new_dict
|
| + def save(self, filename=None):
|
| + """
|
| + Pickle the contents of the Env object into a file.
|
|
|
| + @param filename: Filename to pickle the dict into. If not supplied,
|
| + use the filename from which the dict was loaded.
|
| + """
|
| + filename = filename or self._filename
|
| + f = open(filename, "w")
|
| + cPickle.dump(self.data, f)
|
| + f.close()
|
|
|
| -def get_sub_dict_names(dict, keyword):
|
| - """
|
| - Return a list of "sub-dict" names that may be extracted with get_sub_dict.
|
|
|
| - This function may be modified to change the behavior of all functions that
|
| - deal with multiple objects defined in dicts (e.g. VMs, images, NICs).
|
| + def get_all_vms(self):
|
| + """
|
| + Return a list of all VM objects in this Env object.
|
| + """
|
| + return [o for o in self.values() if is_vm(o)]
|
| +
|
| +
|
| + def get_vm(self, name):
|
| + """
|
| + Return a VM object by its name.
|
| +
|
| + @param name: VM name.
|
| + """
|
| + return self.get("vm__%s" % name)
|
| +
|
| +
|
| + def register_vm(self, name, vm):
|
| + """
|
| + Register a VM in this Env object.
|
| +
|
| + @param name: VM name.
|
| + @param vm: VM object.
|
| + """
|
| + self["vm__%s" % name] = vm
|
| +
|
| +
|
| + def unregister_vm(self, name):
|
| + """
|
| + Remove a given VM.
|
| +
|
| + @param name: VM name.
|
| + """
|
| + del self["vm__%s" % name]
|
| +
|
|
|
| - @param keyword: A key in dict (e.g. "vms", "images", "nics").
|
| + def register_installer(self, installer):
|
| + """
|
| + Register a installer that was just run
|
| +
|
| + The installer will be available for other tests, so that
|
| + information about the installed KVM modules and qemu-kvm can be used by
|
| + them.
|
| + """
|
| + self['last_installer'] = installer
|
| +
|
| +
|
| + def previous_installer(self):
|
| + """
|
| + Return the last installer that was registered
|
| + """
|
| + return self.get('last_installer')
|
| +
|
| +
|
| +class Params(UserDict.IterableUserDict):
|
| """
|
| - names = dict.get(keyword)
|
| - if names:
|
| - return names.split()
|
| - else:
|
| - return []
|
| + A dict-like object passed to every test.
|
| + """
|
| + def objects(self, key):
|
| + """
|
| + Return the names of objects defined using a given key.
|
| +
|
| + @param key: The name of the key whose value lists the objects
|
| + (e.g. 'nics').
|
| + """
|
| + return self.get(key, "").split()
|
| +
|
| +
|
| + def object_params(self, obj_name):
|
| + """
|
| + Return a dict-like object containing the parameters of an individual
|
| + object.
|
| +
|
| + This method behaves as follows: the suffix '_' + obj_name is removed
|
| + from all key names that have it. Other key names are left unchanged.
|
| + The values of keys with the suffix overwrite the values of their
|
| + suffixless versions.
|
| +
|
| + @param obj_name: The name of the object (objects are listed by the
|
| + objects() method).
|
| + """
|
| + suffix = "_" + obj_name
|
| + new_dict = self.copy()
|
| + for key in self:
|
| + if key.endswith(suffix):
|
| + new_key = key.split(suffix)[0]
|
| + new_dict[new_key] = self[key]
|
| + return new_dict
|
|
|
|
|
| # Functions related to MAC/IP addresses
|
| @@ -240,60 +318,6 @@ def verify_ip_address_ownership(ip, macs, timeout=10.0):
|
| return bool(regex.search(o))
|
|
|
|
|
| -# Functions for working with the environment (a dict-like object)
|
| -
|
| -def is_vm(obj):
|
| - """
|
| - Tests whether a given object is a VM object.
|
| -
|
| - @param obj: Python object (pretty much everything on python).
|
| - """
|
| - return obj.__class__.__name__ == "VM"
|
| -
|
| -
|
| -def env_get_all_vms(env):
|
| - """
|
| - Return a list of all VM objects on a given environment.
|
| -
|
| - @param env: Dictionary with environment items.
|
| - """
|
| - vms = []
|
| - for obj in env.values():
|
| - if is_vm(obj):
|
| - vms.append(obj)
|
| - return vms
|
| -
|
| -
|
| -def env_get_vm(env, name):
|
| - """
|
| - Return a VM object by its name.
|
| -
|
| - @param name: VM name.
|
| - """
|
| - return env.get("vm__%s" % name)
|
| -
|
| -
|
| -def env_register_vm(env, name, vm):
|
| - """
|
| - Register a given VM in a given env.
|
| -
|
| - @param env: Environment where we will register the VM.
|
| - @param name: VM name.
|
| - @param vm: VM object.
|
| - """
|
| - env["vm__%s" % name] = vm
|
| -
|
| -
|
| -def env_unregister_vm(env, name):
|
| - """
|
| - Remove a given VM from a given env.
|
| -
|
| - @param env: Environment where we will un-register the VM.
|
| - @param name: VM name.
|
| - """
|
| - del env["vm__%s" % name]
|
| -
|
| -
|
| # Utility functions for dealing with external processes
|
|
|
| def find_command(cmd):
|
| @@ -403,8 +427,7 @@ def get_git_branch(repository, branch, srcdir, commit=None, lbranch=None):
|
| except error.CmdError:
|
| desc = "no tag found"
|
|
|
| - logging.info("Commit hash for %s is %s (%s)" % (repository, h.strip(),
|
| - desc))
|
| + logging.info("Commit hash for %s is %s (%s)", repository, h.strip(), desc)
|
| return srcdir
|
|
|
|
|
| @@ -421,7 +444,7 @@ def check_kvm_source_dir(source_dir):
|
| os.chdir(source_dir)
|
| has_qemu_dir = os.path.isdir('qemu')
|
| has_kvm_dir = os.path.isdir('kvm')
|
| - if has_qemu_dir and not has_kvm_dir:
|
| + if has_qemu_dir:
|
| logging.debug("qemu directory detected, source dir layout 1")
|
| return 1
|
| if has_kvm_dir and not has_qemu_dir:
|
| @@ -431,8 +454,80 @@ def check_kvm_source_dir(source_dir):
|
| raise error.TestError("Unknown source dir layout, cannot proceed.")
|
|
|
|
|
| -# The following are functions used for SSH, SCP and Telnet communication with
|
| -# guests.
|
| +# Functions and classes used for logging into guests and transferring files
|
| +
|
| +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)
|
| +
|
| +
|
| +class LoginAuthenticationError(LoginError):
|
| + pass
|
| +
|
| +
|
| +class LoginTimeoutError(LoginError):
|
| + def __init__(self, output):
|
| + LoginError.__init__(self, "Login timeout expired", output)
|
| +
|
| +
|
| +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))
|
| +
|
| +
|
| +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
|
| +
|
| +
|
| +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)
|
| +
|
| +
|
| +class SCPAuthenticationError(SCPError):
|
| + pass
|
| +
|
| +
|
| +class SCPAuthenticationTimeoutError(SCPAuthenticationError):
|
| + def __init__(self, output):
|
| + SCPAuthenticationError.__init__(self, "Authentication timeout expired",
|
| + output)
|
| +
|
| +
|
| +class SCPTransferTimeoutError(SCPError):
|
| + def __init__(self, output):
|
| + SCPError.__init__(self, "Transfer timeout expired", output)
|
| +
|
| +
|
| +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))
|
| +
|
|
|
| def _remote_login(session, username, password, prompt, timeout=10):
|
| """
|
| @@ -442,116 +537,68 @@ def _remote_login(session, username, password, prompt, timeout=10):
|
|
|
| @brief: Log into a remote host (guest) using SSH or Telnet.
|
|
|
| - @param session: A kvm_expect or kvm_shell_session instance to operate on
|
| + @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)
|
| -
|
| - @return: True on success and False otherwise.
|
| + @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
|
| """
|
| password_prompt_count = 0
|
| login_prompt_count = 0
|
|
|
| while True:
|
| - (match, text) = session.read_until_last_line_matches(
|
| + try:
|
| + match, text = session.read_until_last_line_matches(
|
| [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"[Ll]ogin:\s*$",
|
| r"[Cc]onnection.*closed", r"[Cc]onnection.*refused",
|
| r"[Pp]lease wait", prompt],
|
| - 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)
|
| - session.sendline(password)
|
| - password_prompt_count += 1
|
| - continue
|
| - else:
|
| - logging.debug("Got password prompt again")
|
| - return False
|
| - elif match == 2: # "login:"
|
| - if login_prompt_count == 0:
|
| - logging.debug("Got username prompt; sending '%s'" % username)
|
| - session.sendline(username)
|
| - login_prompt_count += 1
|
| - continue
|
| - else:
|
| - logging.debug("Got username prompt again")
|
| - return False
|
| - elif match == 3: # "Connection closed"
|
| - logging.debug("Got 'Connection closed'")
|
| - return False
|
| - elif match == 4: # "Connection refused"
|
| - logging.debug("Got 'Connection refused'")
|
| - return False
|
| - elif match == 5: # "Please wait"
|
| - logging.debug("Got 'Please wait'")
|
| - timeout = 30
|
| - continue
|
| - elif match == 6: # prompt
|
| - logging.debug("Got shell prompt -- logged in")
|
| - return session
|
| - else: # match == None
|
| - logging.debug("Timeout elapsed or process terminated")
|
| - return False
|
| -
|
| -
|
| -def _remote_scp(session, password, transfer_timeout=600, login_timeout=10):
|
| - """
|
| - 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.
|
| -
|
| - @brief: Transfer files using SCP, given a command line.
|
| -
|
| - @param session: A kvm_expect or kvm_shell_session instance to operate on
|
| - @param password: The password to send in reply to a 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)
|
| -
|
| - @return: True if the transfer succeeds and False on failure.
|
| - """
|
| - password_prompt_count = 0
|
| - timeout = login_timeout
|
| -
|
| - while True:
|
| - (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)
|
| - session.sendline(password)
|
| - password_prompt_count += 1
|
| - timeout = transfer_timeout
|
| + if match == 0: # "Are you sure you want to continue connecting"
|
| + logging.debug("Got 'Are you sure...'; sending 'yes'")
|
| + session.sendline("yes")
|
| continue
|
| - else:
|
| - logging.debug("Got password prompt again")
|
| - return False
|
| - elif match == 2: # "lost connection"
|
| - logging.debug("Got 'lost connection'")
|
| - return False
|
| - else: # match == None
|
| - if session.is_alive():
|
| - logging.debug("Timeout expired")
|
| - return False
|
| - else:
|
| - status = session.get_status()
|
| - logging.debug("SCP process terminated with status %s", status)
|
| - return status == 0
|
| + elif match == 1: # "password:"
|
| + if password_prompt_count == 0:
|
| + 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 == 2: # "login:"
|
| + if login_prompt_count == 0 and password_prompt_count == 0:
|
| + 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 == 3: # "Connection closed"
|
| + raise LoginError("Client said 'connection closed'", text)
|
| + elif match == 4: # "Connection refused"
|
| + raise LoginError("Client said 'connection refused'", text)
|
| + elif match == 5: # "Please wait"
|
| + logging.debug("Got 'Please wait'")
|
| + timeout = 30
|
| + continue
|
| + elif match == 6: # prompt
|
| + logging.debug("Got shell prompt -- logged in")
|
| + break
|
| + except kvm_subprocess.ExpectTimeoutError, e:
|
| + raise LoginTimeoutError(e.output)
|
| + except kvm_subprocess.ExpectProcessTerminatedError, e:
|
| + raise LoginProcessTerminatedError(e.status, e.output)
|
|
|
|
|
| def remote_login(client, host, port, username, password, prompt, linesep="\n",
|
| @@ -571,8 +618,9 @@ def remote_login(client, host, port, username, password, prompt, linesep="\n",
|
| @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)
|
| -
|
| - @return: kvm_shell_session object on success and None on failure.
|
| + @raise LoginBadClientError: If an unknown client is requested
|
| + @raise: Whatever _remote_login() raises
|
| + @return: A ShellSession object.
|
| """
|
| if client == "ssh":
|
| cmd = ("ssh -o UserKnownHostsFile=/dev/null "
|
| @@ -583,19 +631,109 @@ def remote_login(client, host, port, username, password, prompt, linesep="\n",
|
| elif client == "nc":
|
| cmd = "nc %s %s" % (host, port)
|
| else:
|
| - logging.error("Unknown remote shell client: %s" % client)
|
| - return
|
| + raise LoginBadClientError(client)
|
|
|
| - logging.debug("Trying to login with command '%s'" % cmd)
|
| - session = kvm_subprocess.kvm_shell_session(cmd, linesep=linesep,
|
| - prompt=prompt)
|
| - if _remote_login(session, username, password, prompt, timeout):
|
| - if log_filename:
|
| - session.set_output_func(log_line)
|
| - session.set_output_params((log_filename,))
|
| - return session
|
| - else:
|
| + logging.debug("Trying to login with command '%s'", cmd)
|
| + session = kvm_subprocess.ShellSession(cmd, linesep=linesep, prompt=prompt)
|
| + try:
|
| + _remote_login(session, username, password, prompt, timeout)
|
| + except:
|
| session.close()
|
| + raise
|
| + if log_filename:
|
| + session.set_output_func(log_line)
|
| + session.set_output_params((log_filename,))
|
| + return session
|
| +
|
| +
|
| +def wait_for_login(client, host, port, username, password, prompt, linesep="\n",
|
| + log_filename=None, timeout=240, internal_timeout=10):
|
| + """
|
| + Make multiple attempts to log into a remote host (guest) until one succeeds
|
| + or timeout expires.
|
| +
|
| + @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)
|
| + @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)
|
| + 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)
|
| +
|
| +
|
| +def _remote_scp(session, password, transfer_timeout=600, login_timeout=10):
|
| + """
|
| + 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.
|
| +
|
| + @brief: Transfer files using SCP, given a command line.
|
| +
|
| + @param session: An Expect or ShellSession instance to operate on
|
| + @param password: The password to send in reply to a 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
|
| +
|
| + 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)
|
| + session.sendline(password)
|
| + 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 kvm_subprocess.ExpectTimeoutError, e:
|
| + if authentication_done:
|
| + raise SCPTransferTimeoutError(e.output)
|
| + else:
|
| + raise SCPAuthenticationTimeoutError(e.output)
|
| + except kvm_subprocess.ExpectProcessTerminatedError, e:
|
| + if e.status == 0:
|
| + logging.debug("SCP process terminated with status 0")
|
| + break
|
| + else:
|
| + raise SCPTransferFailedError(e.status, e.output)
|
|
|
|
|
| def remote_scp(command, password, log_filename=None, transfer_timeout=600,
|
| @@ -614,24 +752,21 @@ def remote_scp(command, password, log_filename=None, transfer_timeout=600,
|
| @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)
|
| -
|
| - @return: True if the transfer succeeds and False on failure.
|
| + @raise: Whatever _remote_scp() raises
|
| """
|
| logging.debug("Trying to SCP with command '%s', timeout %ss",
|
| command, transfer_timeout)
|
| -
|
| if log_filename:
|
| output_func = log_line
|
| output_params = (log_filename,)
|
| else:
|
| output_func = None
|
| output_params = ()
|
| -
|
| - session = kvm_subprocess.kvm_expect(command,
|
| - output_func=output_func,
|
| - output_params=output_params)
|
| + session = kvm_subprocess.Expect(command,
|
| + output_func=output_func,
|
| + output_params=output_params)
|
| try:
|
| - return _remote_scp(session, password, transfer_timeout, login_timeout)
|
| + _remote_scp(session, password, transfer_timeout, login_timeout)
|
| finally:
|
| session.close()
|
|
|
| @@ -639,7 +774,7 @@ def remote_scp(command, password, log_filename=None, transfer_timeout=600,
|
| def scp_to_remote(host, port, username, password, local_path, remote_path,
|
| log_filename=None, timeout=600):
|
| """
|
| - Copy files to a remote host (guest).
|
| + Copy files to a remote host (guest) through scp.
|
|
|
| @param host: Hostname or IP address
|
| @param username: Username (if required)
|
| @@ -649,13 +784,12 @@ def scp_to_remote(host, port, username, password, local_path, remote_path,
|
| @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.
|
| -
|
| - @return: True on success and False on failure.
|
| + @raise: Whatever remote_scp() raises
|
| """
|
| command = ("scp -v -o UserKnownHostsFile=/dev/null "
|
| "-o PreferredAuthentications=password -r -P %s %s %s@%s:%s" %
|
| (port, local_path, username, host, remote_path))
|
| - return remote_scp(command, password, log_filename, timeout)
|
| + remote_scp(command, password, log_filename, timeout)
|
|
|
|
|
| def scp_from_remote(host, port, username, password, remote_path, local_path,
|
| @@ -671,13 +805,70 @@ def scp_from_remote(host, port, username, password, remote_path, local_path,
|
| @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.
|
| -
|
| - @return: True on success and False on failure.
|
| + @raise: Whatever remote_scp() raises
|
| """
|
| command = ("scp -v -o UserKnownHostsFile=/dev/null "
|
| "-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" %
|
| (port, username, host, remote_path, local_path))
|
| - return remote_scp(command, password, log_filename, timeout)
|
| + remote_scp(command, password, log_filename, timeout)
|
| +
|
| +
|
| +def copy_files_to(address, client, username, password, port, local_path,
|
| + remote_path, log_filename=None, verbose=False, timeout=600):
|
| + """
|
| + 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 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.
|
| + @raise: Whatever remote_scp() raises
|
| + """
|
| + if client == "scp":
|
| + scp_to_remote(address, port, username, password, local_path,
|
| + remote_path, log_filename, timeout)
|
| + elif client == "rss":
|
| + log_func = None
|
| + if verbose:
|
| + log_func = logging.debug
|
| + c = rss_file_transfer.FileUploadClient(address, port, log_func)
|
| + c.upload(local_path, remote_path, timeout)
|
| + c.close()
|
| +
|
| +
|
| +def copy_files_from(address, client, username, password, port, remote_path,
|
| + local_path, log_filename=None, verbose=False, timeout=600):
|
| + """
|
| + 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 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.
|
| + @raise: Whatever remote_scp() raises
|
| + """
|
| + if client == "scp":
|
| + scp_from_remote(address, port, username, password, remote_path,
|
| + local_path, log_filename, timeout)
|
| + elif client == "rss":
|
| + log_func = None
|
| + if verbose:
|
| + log_func = logging.debug
|
| + c = rss_file_transfer.FileDownloadClient(address, port, log_func)
|
| + c.download(remote_path, local_path, timeout)
|
| + c.close()
|
|
|
|
|
| # The following are utility functions related to ports.
|
| @@ -866,7 +1057,7 @@ def wait_for(func, timeout, first=0.0, step=1.0, text=None):
|
|
|
| while time.time() < end_time:
|
| if text:
|
| - logging.debug("%s (%f secs)" % (text, time.time() - start_time))
|
| + logging.debug("%s (%f secs)", text, (time.time() - start_time))
|
|
|
| output = func()
|
| if output:
|
| @@ -978,6 +1169,93 @@ def get_vendor_from_pci_id(pci_id):
|
| return re.sub(":", " ", commands.getoutput(cmd))
|
|
|
|
|
| +class Thread(threading.Thread):
|
| + """
|
| + Run a function in a background thread.
|
| + """
|
| + def __init__(self, target, args=(), kwargs={}):
|
| + """
|
| + Initialize the instance.
|
| +
|
| + @param target: Function to run in the thread.
|
| + @param args: Arguments to pass to target.
|
| + @param kwargs: Keyword arguments to pass to target.
|
| + """
|
| + threading.Thread.__init__(self)
|
| + self._target = target
|
| + self._args = args
|
| + self._kwargs = kwargs
|
| +
|
| +
|
| + def run(self):
|
| + """
|
| + Run target (passed to the constructor). No point in calling this
|
| + function directly. Call start() to make this function run in a new
|
| + thread.
|
| + """
|
| + self._e = None
|
| + self._retval = None
|
| + try:
|
| + try:
|
| + self._retval = self._target(*self._args, **self._kwargs)
|
| + except:
|
| + self._e = sys.exc_info()
|
| + raise
|
| + finally:
|
| + # Avoid circular references (start() may be called only once so
|
| + # it's OK to delete these)
|
| + del self._target, self._args, self._kwargs
|
| +
|
| +
|
| + def join(self, timeout=None, suppress_exception=False):
|
| + """
|
| + Join the thread. If target raised an exception, re-raise it.
|
| + Otherwise, return the value returned by target.
|
| +
|
| + @param timeout: Timeout value to pass to threading.Thread.join().
|
| + @param suppress_exception: If True, don't re-raise the exception.
|
| + """
|
| + threading.Thread.join(self, timeout)
|
| + try:
|
| + if self._e:
|
| + if not suppress_exception:
|
| + # Because the exception was raised in another thread, we
|
| + # need to explicitly insert the current context into it
|
| + s = error.exception_context(self._e[1])
|
| + s = error.join_contexts(error.get_context(), s)
|
| + error.set_exception_context(self._e[1], s)
|
| + raise self._e[0], self._e[1], self._e[2]
|
| + else:
|
| + return self._retval
|
| + finally:
|
| + # Avoid circular references (join() may be called multiple times
|
| + # so we can't delete these)
|
| + self._e = None
|
| + self._retval = None
|
| +
|
| +
|
| +def parallel(targets):
|
| + """
|
| + Run multiple functions in parallel.
|
| +
|
| + @param targets: A sequence of tuples or functions. If it's a sequence of
|
| + tuples, each tuple will be interpreted as (target, args, kwargs) or
|
| + (target, args) or (target,) depending on its length. If it's a
|
| + sequence of functions, the functions will be called without
|
| + arguments.
|
| + @return: A list of the values returned by the functions called.
|
| + """
|
| + threads = []
|
| + for target in targets:
|
| + if isinstance(target, tuple) or isinstance(target, list):
|
| + t = Thread(*target)
|
| + else:
|
| + t = Thread(target)
|
| + threads.append(t)
|
| + t.start()
|
| + return [t.join() for t in threads]
|
| +
|
| +
|
| class KvmLoggingConfig(logging_config.LoggingConfig):
|
| """
|
| Used with the sole purpose of providing convenient logging setup
|
| @@ -1176,8 +1454,8 @@ class PciAssignable(object):
|
| # Re-probe driver with proper number of VFs
|
| if re_probe:
|
| cmd = "modprobe %s %s" % (self.driver, self.driver_option)
|
| - logging.info("Loading the driver '%s' with option '%s'" %
|
| - (self.driver, self.driver_option))
|
| + logging.info("Loading the driver '%s' with option '%s'",
|
| + self.driver, self.driver_option)
|
| s, o = commands.getstatusoutput(cmd)
|
| if s:
|
| return False
|
| @@ -1205,8 +1483,8 @@ class PciAssignable(object):
|
| if not full_id:
|
| continue
|
| drv_path = os.path.join(base_dir, "devices/%s/driver" % full_id)
|
| - dev_prev_driver= os.path.realpath(os.path.join(drv_path,
|
| - os.readlink(drv_path)))
|
| + dev_prev_driver = os.path.realpath(os.path.join(drv_path,
|
| + os.readlink(drv_path)))
|
| self.dev_drivers[pci_id] = dev_prev_driver
|
|
|
| # Judge whether the device driver has been binded to stub
|
| @@ -1347,7 +1625,7 @@ class KojiDownloader(object):
|
| "provide an appropriate tag or build name.")
|
|
|
| if not build:
|
| - builds = self.session.listTagged(tag, latest=True,
|
| + builds = self.session.listTagged(tag, latest=True, inherit=True,
|
| package=src_package)
|
| if not builds:
|
| raise ValueError("Tag %s has no builds of %s" % (tag,
|
| @@ -1390,3 +1668,58 @@ class KojiDownloader(object):
|
| rpm_paths.append(r)
|
|
|
| return rpm_paths
|
| +
|
| +
|
| +def umount(src, mount_point, type):
|
| + """
|
| + Umount the src mounted in mount_point.
|
| +
|
| + @src: mount source
|
| + @mount_point: mount point
|
| + @type: file system type
|
| + """
|
| +
|
| + mount_string = "%s %s %s" % (src, mount_point, type)
|
| + if mount_string in file("/etc/mtab").read():
|
| + umount_cmd = "umount %s" % mount_point
|
| + try:
|
| + utils.system(umount_cmd)
|
| + return True
|
| + except error.CmdError:
|
| + return False
|
| + else:
|
| + logging.debug("%s is not mounted under %s", src, mount_point)
|
| + return True
|
| +
|
| +
|
| +def mount(src, mount_point, type, perm="rw"):
|
| + """
|
| + Mount the src into mount_point of the host.
|
| +
|
| + @src: mount source
|
| + @mount_point: mount point
|
| + @type: file system type
|
| + @perm: mount premission
|
| + """
|
| + umount(src, mount_point, type)
|
| + mount_string = "%s %s %s %s" % (src, mount_point, type, perm)
|
| +
|
| + if mount_string in file("/etc/mtab").read():
|
| + logging.debug("%s is already mounted in %s with %s",
|
| + src, mount_point, perm)
|
| + return True
|
| +
|
| + mount_cmd = "mount -t %s %s %s -o %s" % (type, src, mount_point, perm)
|
| + try:
|
| + utils.system(mount_cmd)
|
| + except error.CmdError:
|
| + return False
|
| +
|
| + logging.debug("Verify the mount through /etc/mtab")
|
| + if mount_string in file("/etc/mtab").read():
|
| + logging.debug("%s is successfully mounted", src)
|
| + return True
|
| + else:
|
| + logging.error("Can't find mounted NFS share - /etc/mtab contents \n%s",
|
| + file("/etc/mtab").read())
|
| + return False
|
|
|