| OLD | NEW |
| 1 """ | 1 """ |
| 2 KVM test utility functions. | 2 KVM test utility functions. |
| 3 | 3 |
| 4 @copyright: 2008-2009 Red Hat Inc. | 4 @copyright: 2008-2009 Red Hat Inc. |
| 5 """ | 5 """ |
| 6 | 6 |
| 7 import time, string, random, socket, os, signal, re, logging, commands, cPickle | 7 import time, string, random, socket, os, signal, re, logging, commands, cPickle |
| 8 import fcntl, shelve, ConfigParser, rss_file_transfer, threading, sys, UserDict | 8 import fcntl, shelve, ConfigParser |
| 9 from autotest_lib.client.bin import utils, os_dep | 9 from autotest_lib.client.bin import utils, os_dep |
| 10 from autotest_lib.client.common_lib import error, logging_config | 10 from autotest_lib.client.common_lib import error, logging_config |
| 11 import kvm_subprocess | 11 import kvm_subprocess |
| 12 try: | 12 try: |
| 13 import koji | 13 import koji |
| 14 KOJI_INSTALLED = True | 14 KOJI_INSTALLED = True |
| 15 except ImportError: | 15 except ImportError: |
| 16 KOJI_INSTALLED = False | 16 KOJI_INSTALLED = False |
| 17 | 17 |
| 18 | 18 |
| 19 def _lock_file(filename): | 19 def _lock_file(filename): |
| 20 f = open(filename, "w") | 20 f = open(filename, "w") |
| 21 fcntl.lockf(f, fcntl.LOCK_EX) | 21 fcntl.lockf(f, fcntl.LOCK_EX) |
| 22 return f | 22 return f |
| 23 | 23 |
| 24 | 24 |
| 25 def _unlock_file(f): | 25 def _unlock_file(f): |
| 26 fcntl.lockf(f, fcntl.LOCK_UN) | 26 fcntl.lockf(f, fcntl.LOCK_UN) |
| 27 f.close() | 27 f.close() |
| 28 | 28 |
| 29 | 29 |
| 30 def is_vm(obj): | 30 def dump_env(obj, filename): |
| 31 """ | 31 """ |
| 32 Tests whether a given object is a VM object. | 32 Dump KVM test environment to a file. |
| 33 | 33 |
| 34 @param obj: Python object. | 34 @param filename: Path to a file where the environment will be dumped to. |
| 35 """ | 35 """ |
| 36 return obj.__class__.__name__ == "VM" | 36 file = open(filename, "w") |
| 37 cPickle.dump(obj, file) |
| 38 file.close() |
| 37 | 39 |
| 38 | 40 |
| 39 class Env(UserDict.IterableUserDict): | 41 def load_env(filename, version): |
| 40 """ | 42 """ |
| 41 A dict-like object containing global objects used by tests. | 43 Load KVM test environment from an env file. |
| 44 If the version recorded in the file is lower than version, return an empty |
| 45 env. If some other error occurs during unpickling, return an empty env. |
| 46 |
| 47 @param filename: Path to an env file. |
| 42 """ | 48 """ |
| 43 def __init__(self, filename=None, version=0): | 49 default = {"version": version} |
| 44 """ | 50 try: |
| 45 Create an empty Env object or load an existing one from a file. | 51 file = open(filename, "r") |
| 46 | 52 env = cPickle.load(file) |
| 47 If the version recorded in the file is lower than version, or if some | 53 file.close() |
| 48 error occurs during unpickling, or if filename is not supplied, | 54 if env.get("version", 0) < version: |
| 49 create an empty Env object. | 55 logging.warn("Incompatible env file found. Not using it.") |
| 50 | 56 return default |
| 51 @param filename: Path to an env file. | 57 return env |
| 52 @param version: Required env version (int). | 58 # Almost any exception can be raised during unpickling, so let's catch |
| 53 """ | 59 # them all |
| 54 UserDict.IterableUserDict.__init__(self) | 60 except Exception, e: |
| 55 empty = {"version": version} | 61 logging.warn(e) |
| 56 if filename: | 62 return default |
| 57 self._filename = filename | |
| 58 try: | |
| 59 f = open(filename, "r") | |
| 60 env = cPickle.load(f) | |
| 61 f.close() | |
| 62 if env.get("version", 0) >= version: | |
| 63 self.data = env | |
| 64 else: | |
| 65 logging.warn("Incompatible env file found. Not using it.") | |
| 66 self.data = empty | |
| 67 # Almost any exception can be raised during unpickling, so let's | |
| 68 # catch them all | |
| 69 except Exception, e: | |
| 70 logging.warn(e) | |
| 71 self.data = empty | |
| 72 else: | |
| 73 self.data = empty | |
| 74 | 63 |
| 75 | 64 |
| 76 def save(self, filename=None): | 65 def get_sub_dict(dict, name): |
| 77 """ | 66 """ |
| 78 Pickle the contents of the Env object into a file. | 67 Return a "sub-dict" corresponding to a specific object. |
| 79 | 68 |
| 80 @param filename: Filename to pickle the dict into. If not supplied, | 69 Operate on a copy of dict: for each key that ends with the suffix |
| 81 use the filename from which the dict was loaded. | 70 "_" + name, strip the suffix from the key, and set the value of |
| 82 """ | 71 the stripped key to that of the key. Return the resulting dict. |
| 83 filename = filename or self._filename | 72 |
| 84 f = open(filename, "w") | 73 @param name: Suffix of the key we want to set the value. |
| 85 cPickle.dump(self.data, f) | 74 """ |
| 86 f.close() | 75 suffix = "_" + name |
| 76 new_dict = dict.copy() |
| 77 for key in dict.keys(): |
| 78 if key.endswith(suffix): |
| 79 new_key = key.split(suffix)[0] |
| 80 new_dict[new_key] = dict[key] |
| 81 return new_dict |
| 87 | 82 |
| 88 | 83 |
| 89 def get_all_vms(self): | 84 def get_sub_dict_names(dict, keyword): |
| 90 """ | 85 """ |
| 91 Return a list of all VM objects in this Env object. | 86 Return a list of "sub-dict" names that may be extracted with get_sub_dict. |
| 92 """ | |
| 93 return [o for o in self.values() if is_vm(o)] | |
| 94 | 87 |
| 88 This function may be modified to change the behavior of all functions that |
| 89 deal with multiple objects defined in dicts (e.g. VMs, images, NICs). |
| 95 | 90 |
| 96 def get_vm(self, name): | 91 @param keyword: A key in dict (e.g. "vms", "images", "nics"). |
| 97 """ | |
| 98 Return a VM object by its name. | |
| 99 | |
| 100 @param name: VM name. | |
| 101 """ | |
| 102 return self.get("vm__%s" % name) | |
| 103 | |
| 104 | |
| 105 def register_vm(self, name, vm): | |
| 106 """ | |
| 107 Register a VM in this Env object. | |
| 108 | |
| 109 @param name: VM name. | |
| 110 @param vm: VM object. | |
| 111 """ | |
| 112 self["vm__%s" % name] = vm | |
| 113 | |
| 114 | |
| 115 def unregister_vm(self, name): | |
| 116 """ | |
| 117 Remove a given VM. | |
| 118 | |
| 119 @param name: VM name. | |
| 120 """ | |
| 121 del self["vm__%s" % name] | |
| 122 | |
| 123 | |
| 124 def register_installer(self, installer): | |
| 125 """ | |
| 126 Register a installer that was just run | |
| 127 | |
| 128 The installer will be available for other tests, so that | |
| 129 information about the installed KVM modules and qemu-kvm can be used by | |
| 130 them. | |
| 131 """ | |
| 132 self['last_installer'] = installer | |
| 133 | |
| 134 | |
| 135 def previous_installer(self): | |
| 136 """ | |
| 137 Return the last installer that was registered | |
| 138 """ | |
| 139 return self.get('last_installer') | |
| 140 | |
| 141 | |
| 142 class Params(UserDict.IterableUserDict): | |
| 143 """ | 92 """ |
| 144 A dict-like object passed to every test. | 93 names = dict.get(keyword) |
| 145 """ | 94 if names: |
| 146 def objects(self, key): | 95 return names.split() |
| 147 """ | 96 else: |
| 148 Return the names of objects defined using a given key. | 97 return [] |
| 149 | |
| 150 @param key: The name of the key whose value lists the objects | |
| 151 (e.g. 'nics'). | |
| 152 """ | |
| 153 return self.get(key, "").split() | |
| 154 | |
| 155 | |
| 156 def object_params(self, obj_name): | |
| 157 """ | |
| 158 Return a dict-like object containing the parameters of an individual | |
| 159 object. | |
| 160 | |
| 161 This method behaves as follows: the suffix '_' + obj_name is removed | |
| 162 from all key names that have it. Other key names are left unchanged. | |
| 163 The values of keys with the suffix overwrite the values of their | |
| 164 suffixless versions. | |
| 165 | |
| 166 @param obj_name: The name of the object (objects are listed by the | |
| 167 objects() method). | |
| 168 """ | |
| 169 suffix = "_" + obj_name | |
| 170 new_dict = self.copy() | |
| 171 for key in self: | |
| 172 if key.endswith(suffix): | |
| 173 new_key = key.split(suffix)[0] | |
| 174 new_dict[new_key] = self[key] | |
| 175 return new_dict | |
| 176 | 98 |
| 177 | 99 |
| 178 # Functions related to MAC/IP addresses | 100 # Functions related to MAC/IP addresses |
| 179 | 101 |
| 180 def _open_mac_pool(lock_mode): | 102 def _open_mac_pool(lock_mode): |
| 181 lock_file = open("/tmp/mac_lock", "w+") | 103 lock_file = open("/tmp/mac_lock", "w+") |
| 182 fcntl.lockf(lock_file, lock_mode) | 104 fcntl.lockf(lock_file, lock_mode) |
| 183 pool = shelve.open("/tmp/address_pool") | 105 pool = shelve.open("/tmp/address_pool") |
| 184 return pool, lock_file | 106 return pool, lock_file |
| 185 | 107 |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 311 if not dev: | 233 if not dev: |
| 312 return False | 234 return False |
| 313 dev = dev[0].split()[-1] | 235 dev = dev[0].split()[-1] |
| 314 | 236 |
| 315 # Send an ARP request | 237 # Send an ARP request |
| 316 o = commands.getoutput("%s -f -c 3 -I %s %s" % | 238 o = commands.getoutput("%s -f -c 3 -I %s %s" % |
| 317 (find_command("arping"), dev, ip)) | 239 (find_command("arping"), dev, ip)) |
| 318 return bool(regex.search(o)) | 240 return bool(regex.search(o)) |
| 319 | 241 |
| 320 | 242 |
| 243 # Functions for working with the environment (a dict-like object) |
| 244 |
| 245 def is_vm(obj): |
| 246 """ |
| 247 Tests whether a given object is a VM object. |
| 248 |
| 249 @param obj: Python object (pretty much everything on python). |
| 250 """ |
| 251 return obj.__class__.__name__ == "VM" |
| 252 |
| 253 |
| 254 def env_get_all_vms(env): |
| 255 """ |
| 256 Return a list of all VM objects on a given environment. |
| 257 |
| 258 @param env: Dictionary with environment items. |
| 259 """ |
| 260 vms = [] |
| 261 for obj in env.values(): |
| 262 if is_vm(obj): |
| 263 vms.append(obj) |
| 264 return vms |
| 265 |
| 266 |
| 267 def env_get_vm(env, name): |
| 268 """ |
| 269 Return a VM object by its name. |
| 270 |
| 271 @param name: VM name. |
| 272 """ |
| 273 return env.get("vm__%s" % name) |
| 274 |
| 275 |
| 276 def env_register_vm(env, name, vm): |
| 277 """ |
| 278 Register a given VM in a given env. |
| 279 |
| 280 @param env: Environment where we will register the VM. |
| 281 @param name: VM name. |
| 282 @param vm: VM object. |
| 283 """ |
| 284 env["vm__%s" % name] = vm |
| 285 |
| 286 |
| 287 def env_unregister_vm(env, name): |
| 288 """ |
| 289 Remove a given VM from a given env. |
| 290 |
| 291 @param env: Environment where we will un-register the VM. |
| 292 @param name: VM name. |
| 293 """ |
| 294 del env["vm__%s" % name] |
| 295 |
| 296 |
| 321 # Utility functions for dealing with external processes | 297 # Utility functions for dealing with external processes |
| 322 | 298 |
| 323 def find_command(cmd): | 299 def find_command(cmd): |
| 324 for dir in ["/usr/local/sbin", "/usr/local/bin", | 300 for dir in ["/usr/local/sbin", "/usr/local/bin", |
| 325 "/usr/sbin", "/usr/bin", "/sbin", "/bin"]: | 301 "/usr/sbin", "/usr/bin", "/sbin", "/bin"]: |
| 326 file = os.path.join(dir, cmd) | 302 file = os.path.join(dir, cmd) |
| 327 if os.path.exists(file): | 303 if os.path.exists(file): |
| 328 return file | 304 return file |
| 329 raise ValueError('Missing command: %s' % cmd) | 305 raise ValueError('Missing command: %s' % cmd) |
| 330 | 306 |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 438 occasions build may be dependant on the source directory disposition. | 414 occasions build may be dependant on the source directory disposition. |
| 439 The reason why the return codes are numbers is that we might have more | 415 The reason why the return codes are numbers is that we might have more |
| 440 changes on the source directory layout, so it's not scalable to just use | 416 changes on the source directory layout, so it's not scalable to just use |
| 441 strings like 'old_repo', 'new_repo' and such. | 417 strings like 'old_repo', 'new_repo' and such. |
| 442 | 418 |
| 443 @param source_dir: Source code path that will be inspected. | 419 @param source_dir: Source code path that will be inspected. |
| 444 """ | 420 """ |
| 445 os.chdir(source_dir) | 421 os.chdir(source_dir) |
| 446 has_qemu_dir = os.path.isdir('qemu') | 422 has_qemu_dir = os.path.isdir('qemu') |
| 447 has_kvm_dir = os.path.isdir('kvm') | 423 has_kvm_dir = os.path.isdir('kvm') |
| 448 if has_qemu_dir: | 424 if has_qemu_dir and not has_kvm_dir: |
| 449 logging.debug("qemu directory detected, source dir layout 1") | 425 logging.debug("qemu directory detected, source dir layout 1") |
| 450 return 1 | 426 return 1 |
| 451 if has_kvm_dir and not has_qemu_dir: | 427 if has_kvm_dir and not has_qemu_dir: |
| 452 logging.debug("kvm directory detected, source dir layout 2") | 428 logging.debug("kvm directory detected, source dir layout 2") |
| 453 return 2 | 429 return 2 |
| 454 else: | 430 else: |
| 455 raise error.TestError("Unknown source dir layout, cannot proceed.") | 431 raise error.TestError("Unknown source dir layout, cannot proceed.") |
| 456 | 432 |
| 457 | 433 |
| 458 # The following are functions used for SSH, SCP and Telnet communication with | 434 # The following are functions used for SSH, SCP and Telnet communication with |
| 459 # guests. | 435 # guests. |
| 460 | 436 |
| 461 def _remote_login(session, username, password, prompt, timeout=10): | 437 def _remote_login(session, username, password, prompt, timeout=10): |
| 462 """ | 438 """ |
| 463 Log into a remote host (guest) using SSH or Telnet. Wait for questions | 439 Log into a remote host (guest) using SSH or Telnet. Wait for questions |
| 464 and provide answers. If timeout expires while waiting for output from the | 440 and provide answers. If timeout expires while waiting for output from the |
| 465 child (e.g. a password prompt or a shell prompt) -- fail. | 441 child (e.g. a password prompt or a shell prompt) -- fail. |
| 466 | 442 |
| 467 @brief: Log into a remote host (guest) using SSH or Telnet. | 443 @brief: Log into a remote host (guest) using SSH or Telnet. |
| 468 | 444 |
| 469 @param session: An Expect or ShellSession instance to operate on | 445 @param session: A kvm_expect or kvm_shell_session instance to operate on |
| 470 @param username: The username to send in reply to a login prompt | 446 @param username: The username to send in reply to a login prompt |
| 471 @param password: The password to send in reply to a password prompt | 447 @param password: The password to send in reply to a password prompt |
| 472 @param prompt: The shell prompt that indicates a successful login | 448 @param prompt: The shell prompt that indicates a successful login |
| 473 @param timeout: The maximal time duration (in seconds) to wait for each | 449 @param timeout: The maximal time duration (in seconds) to wait for each |
| 474 step of the login procedure (i.e. the "Are you sure" prompt, the | 450 step of the login procedure (i.e. the "Are you sure" prompt, the |
| 475 password prompt, the shell prompt, etc) | 451 password prompt, the shell prompt, etc) |
| 476 | 452 |
| 477 @return: True on success and False otherwise. | 453 @return: True on success and False otherwise. |
| 478 """ | 454 """ |
| 479 password_prompt_count = 0 | 455 password_prompt_count = 0 |
| 480 login_prompt_count = 0 | 456 login_prompt_count = 0 |
| 481 | 457 |
| 482 while True: | 458 while True: |
| 483 try: | 459 (match, text) = session.read_until_last_line_matches( |
| 484 match, text = session.read_until_last_line_matches( | |
| 485 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"[Ll]ogin:\s*$", | 460 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"[Ll]ogin:\s*$", |
| 486 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", | 461 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", |
| 487 r"[Pp]lease wait", prompt], | 462 r"[Pp]lease wait", prompt], |
| 488 timeout=timeout, internal_timeout=0.5) | 463 timeout=timeout, internal_timeout=0.5) |
| 489 if match == 0: # "Are you sure you want to continue connecting" | 464 if match == 0: # "Are you sure you want to continue connecting" |
| 490 logging.debug("Got 'Are you sure...'; sending 'yes'") | 465 logging.debug("Got 'Are you sure...'; sending 'yes'") |
| 491 session.sendline("yes") | 466 session.sendline("yes") |
| 467 continue |
| 468 elif match == 1: # "password:" |
| 469 if password_prompt_count == 0: |
| 470 logging.debug("Got password prompt; sending '%s'" % password) |
| 471 session.sendline(password) |
| 472 password_prompt_count += 1 |
| 492 continue | 473 continue |
| 493 elif match == 1: # "password:" | 474 else: |
| 494 if password_prompt_count == 0: | 475 logging.debug("Got password prompt again") |
| 495 logging.debug("Got password prompt; sending '%s'" % password
) | |
| 496 session.sendline(password) | |
| 497 password_prompt_count += 1 | |
| 498 continue | |
| 499 else: | |
| 500 logging.debug("Got password prompt again") | |
| 501 return False | |
| 502 elif match == 2: # "login:" | |
| 503 if login_prompt_count == 0: | |
| 504 logging.debug("Got username prompt; sending '%s'" % username
) | |
| 505 session.sendline(username) | |
| 506 login_prompt_count += 1 | |
| 507 continue | |
| 508 else: | |
| 509 logging.debug("Got username prompt again") | |
| 510 return False | |
| 511 elif match == 3: # "Connection closed" | |
| 512 logging.debug("Got 'Connection closed'") | |
| 513 return False | 476 return False |
| 514 elif match == 4: # "Connection refused" | 477 elif match == 2: # "login:" |
| 515 logging.debug("Got 'Connection refused'") | 478 if login_prompt_count == 0: |
| 479 logging.debug("Got username prompt; sending '%s'" % username) |
| 480 session.sendline(username) |
| 481 login_prompt_count += 1 |
| 482 continue |
| 483 else: |
| 484 logging.debug("Got username prompt again") |
| 516 return False | 485 return False |
| 517 elif match == 5: # "Please wait" | 486 elif match == 3: # "Connection closed" |
| 518 logging.debug("Got 'Please wait'") | 487 logging.debug("Got 'Connection closed'") |
| 519 timeout = 30 | |
| 520 continue | |
| 521 elif match == 6: # prompt | |
| 522 logging.debug("Got shell prompt -- logged in") | |
| 523 return True | |
| 524 except kvm_subprocess.ExpectTimeoutError, e: | |
| 525 logging.debug("Timeout elapsed (output so far: %r)" % e.output) | |
| 526 return False | 488 return False |
| 527 except kvm_subprocess.ExpectProcessTerminatedError, e: | 489 elif match == 4: # "Connection refused" |
| 528 logging.debug("Process terminated (output so far: %r)" % e.output) | 490 logging.debug("Got 'Connection refused'") |
| 491 return False |
| 492 elif match == 5: # "Please wait" |
| 493 logging.debug("Got 'Please wait'") |
| 494 timeout = 30 |
| 495 continue |
| 496 elif match == 6: # prompt |
| 497 logging.debug("Got shell prompt -- logged in") |
| 498 return session |
| 499 else: # match == None |
| 500 logging.debug("Timeout elapsed or process terminated") |
| 529 return False | 501 return False |
| 530 | 502 |
| 531 | 503 |
| 532 def _remote_scp(session, password, transfer_timeout=600, login_timeout=10): | 504 def _remote_scp(session, password, transfer_timeout=600, login_timeout=10): |
| 533 """ | 505 """ |
| 534 Transfer file(s) to a remote host (guest) using SCP. Wait for questions | 506 Transfer file(s) to a remote host (guest) using SCP. Wait for questions |
| 535 and provide answers. If login_timeout expires while waiting for output | 507 and provide answers. If login_timeout expires while waiting for output |
| 536 from the child (e.g. a password prompt), fail. If transfer_timeout expires | 508 from the child (e.g. a password prompt), fail. If transfer_timeout expires |
| 537 while waiting for the transfer to complete, fail. | 509 while waiting for the transfer to complete, fail. |
| 538 | 510 |
| 539 @brief: Transfer files using SCP, given a command line. | 511 @brief: Transfer files using SCP, given a command line. |
| 540 | 512 |
| 541 @param session: An Expect or ShellSession instance to operate on | 513 @param session: A kvm_expect or kvm_shell_session instance to operate on |
| 542 @param password: The password to send in reply to a password prompt. | 514 @param password: The password to send in reply to a password prompt. |
| 543 @param transfer_timeout: The time duration (in seconds) to wait for the | 515 @param transfer_timeout: The time duration (in seconds) to wait for the |
| 544 transfer to complete. | 516 transfer to complete. |
| 545 @param login_timeout: The maximal time duration (in seconds) to wait for | 517 @param login_timeout: The maximal time duration (in seconds) to wait for |
| 546 each step of the login procedure (i.e. the "Are you sure" prompt or | 518 each step of the login procedure (i.e. the "Are you sure" prompt or |
| 547 the password prompt) | 519 the password prompt) |
| 548 | 520 |
| 549 @return: True if the transfer succeeds and False on failure. | 521 @return: True if the transfer succeeds and False on failure. |
| 550 """ | 522 """ |
| 551 password_prompt_count = 0 | 523 password_prompt_count = 0 |
| 552 timeout = login_timeout | 524 timeout = login_timeout |
| 553 | 525 |
| 554 while True: | 526 while True: |
| 555 try: | 527 (match, text) = session.read_until_last_line_matches( |
| 556 match, text = session.read_until_last_line_matches( | |
| 557 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"], | 528 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"], |
| 558 timeout=timeout, internal_timeout=0.5) | 529 timeout=timeout, internal_timeout=0.5) |
| 559 if match == 0: # "Are you sure you want to continue connecting" | 530 if match == 0: # "Are you sure you want to continue connecting" |
| 560 logging.debug("Got 'Are you sure...'; sending 'yes'") | 531 logging.debug("Got 'Are you sure...'; sending 'yes'") |
| 561 session.sendline("yes") | 532 session.sendline("yes") |
| 533 continue |
| 534 elif match == 1: # "password:" |
| 535 if password_prompt_count == 0: |
| 536 logging.debug("Got password prompt; sending '%s'" % password) |
| 537 session.sendline(password) |
| 538 password_prompt_count += 1 |
| 539 timeout = transfer_timeout |
| 562 continue | 540 continue |
| 563 elif match == 1: # "password:" | 541 else: |
| 564 if password_prompt_count == 0: | 542 logging.debug("Got password prompt again") |
| 565 logging.debug("Got password prompt; sending '%s'" % password
) | |
| 566 session.sendline(password) | |
| 567 password_prompt_count += 1 | |
| 568 timeout = transfer_timeout | |
| 569 continue | |
| 570 else: | |
| 571 logging.debug("Got password prompt again") | |
| 572 return False | |
| 573 elif match == 2: # "lost connection" | |
| 574 logging.debug("Got 'lost connection'") | |
| 575 return False | 543 return False |
| 576 except kvm_subprocess.ExpectTimeoutError, e: | 544 elif match == 2: # "lost connection" |
| 577 logging.debug("Timeout expired") | 545 logging.debug("Got 'lost connection'") |
| 578 return False | 546 return False |
| 579 except kvm_subprocess.ExpectProcessTerminatedError, e: | 547 else: # match == None |
| 580 logging.debug("SCP process terminated with status %s", e.status) | 548 if session.is_alive(): |
| 581 return e.status == 0 | 549 logging.debug("Timeout expired") |
| 550 return False |
| 551 else: |
| 552 status = session.get_status() |
| 553 logging.debug("SCP process terminated with status %s", status) |
| 554 return status == 0 |
| 582 | 555 |
| 583 | 556 |
| 584 def remote_login(client, host, port, username, password, prompt, linesep="\n", | 557 def remote_login(client, host, port, username, password, prompt, linesep="\n", |
| 585 log_filename=None, timeout=10): | 558 log_filename=None, timeout=10): |
| 586 """ | 559 """ |
| 587 Log into a remote host (guest) using SSH/Telnet/Netcat. | 560 Log into a remote host (guest) using SSH/Telnet/Netcat. |
| 588 | 561 |
| 589 @param client: The client to use ('ssh', 'telnet' or 'nc') | 562 @param client: The client to use ('ssh', 'telnet' or 'nc') |
| 590 @param host: Hostname or IP address | 563 @param host: Hostname or IP address |
| 591 @param port: Port to connect to | 564 @param port: Port to connect to |
| 592 @param username: Username (if required) | 565 @param username: Username (if required) |
| 593 @param password: Password (if required) | 566 @param password: Password (if required) |
| 594 @param prompt: Shell prompt (regular expression) | 567 @param prompt: Shell prompt (regular expression) |
| 595 @param linesep: The line separator to use when sending lines | 568 @param linesep: The line separator to use when sending lines |
| 596 (e.g. '\\n' or '\\r\\n') | 569 (e.g. '\\n' or '\\r\\n') |
| 597 @param log_filename: If specified, log all output to this file | 570 @param log_filename: If specified, log all output to this file |
| 598 @param timeout: The maximal time duration (in seconds) to wait for | 571 @param timeout: The maximal time duration (in seconds) to wait for |
| 599 each step of the login procedure (i.e. the "Are you sure" prompt | 572 each step of the login procedure (i.e. the "Are you sure" prompt |
| 600 or the password prompt) | 573 or the password prompt) |
| 601 | 574 |
| 602 @return: ShellSession object on success and None on failure. | 575 @return: kvm_shell_session object on success and None on failure. |
| 603 """ | 576 """ |
| 604 if client == "ssh": | 577 if client == "ssh": |
| 605 cmd = ("ssh -o UserKnownHostsFile=/dev/null " | 578 cmd = ("ssh -o UserKnownHostsFile=/dev/null " |
| 606 "-o PreferredAuthentications=password -p %s %s@%s" % | 579 "-o PreferredAuthentications=password -p %s %s@%s" % |
| 607 (port, username, host)) | 580 (port, username, host)) |
| 608 elif client == "telnet": | 581 elif client == "telnet": |
| 609 cmd = "telnet -l %s %s %s" % (username, host, port) | 582 cmd = "telnet -l %s %s %s" % (username, host, port) |
| 610 elif client == "nc": | 583 elif client == "nc": |
| 611 cmd = "nc %s %s" % (host, port) | 584 cmd = "nc %s %s" % (host, port) |
| 612 else: | 585 else: |
| 613 logging.error("Unknown remote shell client: %s" % client) | 586 logging.error("Unknown remote shell client: %s" % client) |
| 614 return | 587 return |
| 615 | 588 |
| 616 logging.debug("Trying to login with command '%s'" % cmd) | 589 logging.debug("Trying to login with command '%s'" % cmd) |
| 617 session = kvm_subprocess.ShellSession(cmd, linesep=linesep, prompt=prompt) | 590 session = kvm_subprocess.kvm_shell_session(cmd, linesep=linesep, |
| 591 prompt=prompt) |
| 618 if _remote_login(session, username, password, prompt, timeout): | 592 if _remote_login(session, username, password, prompt, timeout): |
| 619 if log_filename: | 593 if log_filename: |
| 620 session.set_output_func(log_line) | 594 session.set_output_func(log_line) |
| 621 session.set_output_params((log_filename,)) | 595 session.set_output_params((log_filename,)) |
| 622 return session | 596 return session |
| 623 else: | 597 else: |
| 624 session.close() | 598 session.close() |
| 625 | 599 |
| 626 | 600 |
| 627 def remote_scp(command, password, log_filename=None, transfer_timeout=600, | 601 def remote_scp(command, password, log_filename=None, transfer_timeout=600, |
| (...skipping 18 matching lines...) Expand all Loading... |
| 646 logging.debug("Trying to SCP with command '%s', timeout %ss", | 620 logging.debug("Trying to SCP with command '%s', timeout %ss", |
| 647 command, transfer_timeout) | 621 command, transfer_timeout) |
| 648 | 622 |
| 649 if log_filename: | 623 if log_filename: |
| 650 output_func = log_line | 624 output_func = log_line |
| 651 output_params = (log_filename,) | 625 output_params = (log_filename,) |
| 652 else: | 626 else: |
| 653 output_func = None | 627 output_func = None |
| 654 output_params = () | 628 output_params = () |
| 655 | 629 |
| 656 session = kvm_subprocess.Expect(command, | 630 session = kvm_subprocess.kvm_expect(command, |
| 657 output_func=output_func, | 631 output_func=output_func, |
| 658 output_params=output_params) | 632 output_params=output_params) |
| 659 try: | 633 try: |
| 660 return _remote_scp(session, password, transfer_timeout, login_timeout) | 634 return _remote_scp(session, password, transfer_timeout, login_timeout) |
| 661 finally: | 635 finally: |
| 662 session.close() | 636 session.close() |
| 663 | 637 |
| 664 | 638 |
| 665 def copy_files_to(address, client, username, password, port, local_path, | |
| 666 remote_path, log_filename=None, timeout=600): | |
| 667 """ | |
| 668 Decide the transfer cleint and copy file to a remote host (guest). | |
| 669 | |
| 670 @param client: Type of transfer client | |
| 671 @param username: Username (if required) | |
| 672 @param password: Password (if requried) | |
| 673 @param local_path: Path on the local machine where we are copying from | |
| 674 @param remote_path: Path on the remote machine where we are copying to | |
| 675 @param address: Address of remote host(guest) | |
| 676 @param log_filename: If specified, log all output to this file | |
| 677 @param timeout: The time duration (in seconds) to wait for the transfer to | |
| 678 complete. | |
| 679 | |
| 680 @return: True on success and False on failure. | |
| 681 """ | |
| 682 | |
| 683 if not address or not port: | |
| 684 logging.debug("IP address or port unavailable") | |
| 685 return None | |
| 686 | |
| 687 if client == "scp": | |
| 688 return scp_to_remote(address, port, username, password, local_path, | |
| 689 remote_path, log_filename, timeout) | |
| 690 elif client == "rss": | |
| 691 c = rss_file_transfer.FileUploadClient(address, port) | |
| 692 c.upload(local_path, remote_path, timeout) | |
| 693 c.close() | |
| 694 return True | |
| 695 | |
| 696 | |
| 697 def copy_files_from(address, client, username, password, port, local_path, | |
| 698 remote_path, log_filename=None, timeout=600): | |
| 699 """ | |
| 700 Decide the transfer cleint and copy file from a remote host (guest). | |
| 701 | |
| 702 @param client: Type of transfer client | |
| 703 @param username: Username (if required) | |
| 704 @param password: Password (if requried) | |
| 705 @param local_path: Path on the local machine where we are copying from | |
| 706 @param remote_path: Path on the remote machine where we are copying to | |
| 707 @param address: Address of remote host(guest) | |
| 708 @param log_filename: If specified, log all output to this file | |
| 709 @param timeout: The time duration (in seconds) to wait for the transfer to | |
| 710 complete. | |
| 711 | |
| 712 @return: True on success and False on failure. | |
| 713 """ | |
| 714 | |
| 715 if not address or not port: | |
| 716 logging.debug("IP address or port unavailable") | |
| 717 return None | |
| 718 | |
| 719 if client == "scp": | |
| 720 return scp_from_remote(address, port, username, password, remote_path, | |
| 721 local_path, log_filename, timeout) | |
| 722 elif client == "rss": | |
| 723 c = rss_file_transfer.FileDownloadClient(address, port) | |
| 724 c.download(remote_path, local_path, timeout) | |
| 725 c.close() | |
| 726 return True | |
| 727 | |
| 728 | |
| 729 def scp_to_remote(host, port, username, password, local_path, remote_path, | 639 def scp_to_remote(host, port, username, password, local_path, remote_path, |
| 730 log_filename=None, timeout=600): | 640 log_filename=None, timeout=600): |
| 731 """ | 641 """ |
| 732 Copy files to a remote host (guest) through scp. | 642 Copy files to a remote host (guest). |
| 733 | 643 |
| 734 @param host: Hostname or IP address | 644 @param host: Hostname or IP address |
| 735 @param username: Username (if required) | 645 @param username: Username (if required) |
| 736 @param password: Password (if required) | 646 @param password: Password (if required) |
| 737 @param local_path: Path on the local machine where we are copying from | 647 @param local_path: Path on the local machine where we are copying from |
| 738 @param remote_path: Path on the remote machine where we are copying to | 648 @param remote_path: Path on the remote machine where we are copying to |
| 739 @param log_filename: If specified, log all output to this file | 649 @param log_filename: If specified, log all output to this file |
| 740 @param timeout: The time duration (in seconds) to wait for the transfer | 650 @param timeout: The time duration (in seconds) to wait for the transfer |
| 741 to complete. | 651 to complete. |
| 742 | 652 |
| (...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1061 def get_vendor_from_pci_id(pci_id): | 971 def get_vendor_from_pci_id(pci_id): |
| 1062 """ | 972 """ |
| 1063 Check out the device vendor ID according to pci_id. | 973 Check out the device vendor ID according to pci_id. |
| 1064 | 974 |
| 1065 @param pci_id: PCI ID of a device. | 975 @param pci_id: PCI ID of a device. |
| 1066 """ | 976 """ |
| 1067 cmd = "lspci -n | awk '/%s/ {print $3}'" % pci_id | 977 cmd = "lspci -n | awk '/%s/ {print $3}'" % pci_id |
| 1068 return re.sub(":", " ", commands.getoutput(cmd)) | 978 return re.sub(":", " ", commands.getoutput(cmd)) |
| 1069 | 979 |
| 1070 | 980 |
| 1071 class Thread(threading.Thread): | |
| 1072 """ | |
| 1073 Run a function in a background thread. | |
| 1074 """ | |
| 1075 def __init__(self, target, args=(), kwargs={}): | |
| 1076 """ | |
| 1077 Initialize the instance. | |
| 1078 | |
| 1079 @param target: Function to run in the thread. | |
| 1080 @param args: Arguments to pass to target. | |
| 1081 @param kwargs: Keyword arguments to pass to target. | |
| 1082 """ | |
| 1083 threading.Thread.__init__(self) | |
| 1084 self._target = target | |
| 1085 self._args = args | |
| 1086 self._kwargs = kwargs | |
| 1087 | |
| 1088 | |
| 1089 def run(self): | |
| 1090 """ | |
| 1091 Run target (passed to the constructor). No point in calling this | |
| 1092 function directly. Call start() to make this function run in a new | |
| 1093 thread. | |
| 1094 """ | |
| 1095 self._e = None | |
| 1096 self._retval = None | |
| 1097 try: | |
| 1098 try: | |
| 1099 self._retval = self._target(*self._args, **self._kwargs) | |
| 1100 except: | |
| 1101 self._e = sys.exc_info() | |
| 1102 raise | |
| 1103 finally: | |
| 1104 # Avoid circular references (start() may be called only once so | |
| 1105 # it's OK to delete these) | |
| 1106 del self._target, self._args, self._kwargs | |
| 1107 | |
| 1108 | |
| 1109 def join(self, timeout=None): | |
| 1110 """ | |
| 1111 Join the thread. If target raised an exception, re-raise it. | |
| 1112 Otherwise, return the value returned by target. | |
| 1113 | |
| 1114 @param timeout: Timeout value to pass to threading.Thread.join(). | |
| 1115 """ | |
| 1116 threading.Thread.join(self, timeout) | |
| 1117 try: | |
| 1118 if self._e: | |
| 1119 raise self._e[0], self._e[1], self._e[2] | |
| 1120 else: | |
| 1121 return self._retval | |
| 1122 finally: | |
| 1123 # Avoid circular references (join() may be called multiple times | |
| 1124 # so we can't delete these) | |
| 1125 self._e = None | |
| 1126 self._retval = None | |
| 1127 | |
| 1128 | |
| 1129 def parallel(targets): | |
| 1130 """ | |
| 1131 Run multiple functions in parallel. | |
| 1132 | |
| 1133 @param targets: A sequence of tuples or functions. If it's a sequence of | |
| 1134 tuples, each tuple will be interpreted as (target, args, kwargs) or | |
| 1135 (target, args) or (target,) depending on its length. If it's a | |
| 1136 sequence of functions, the functions will be called without | |
| 1137 arguments. | |
| 1138 @return: A list of the values returned by the functions called. | |
| 1139 """ | |
| 1140 threads = [] | |
| 1141 for target in targets: | |
| 1142 if isinstance(target, tuple) or isinstance(target, list): | |
| 1143 t = Thread(*target) | |
| 1144 else: | |
| 1145 t = Thread(target) | |
| 1146 threads.append(t) | |
| 1147 t.start() | |
| 1148 return [t.join() for t in threads] | |
| 1149 | |
| 1150 | |
| 1151 class KvmLoggingConfig(logging_config.LoggingConfig): | 981 class KvmLoggingConfig(logging_config.LoggingConfig): |
| 1152 """ | 982 """ |
| 1153 Used with the sole purpose of providing convenient logging setup | 983 Used with the sole purpose of providing convenient logging setup |
| 1154 for the KVM test auxiliary programs. | 984 for the KVM test auxiliary programs. |
| 1155 """ | 985 """ |
| 1156 def configure_logging(self, results_dir=None, verbose=False): | 986 def configure_logging(self, results_dir=None, verbose=False): |
| 1157 super(KvmLoggingConfig, self).configure_logging(use_console=True, | 987 super(KvmLoggingConfig, self).configure_logging(use_console=True, |
| 1158 verbose=verbose) | 988 verbose=verbose) |
| 1159 | 989 |
| 1160 | 990 |
| (...skipping 349 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1510 if tag and build: | 1340 if tag and build: |
| 1511 logging.info("Both tag and build parameters provided, ignoring tag " | 1341 logging.info("Both tag and build parameters provided, ignoring tag " |
| 1512 "parameter...") | 1342 "parameter...") |
| 1513 | 1343 |
| 1514 if not tag and not build: | 1344 if not tag and not build: |
| 1515 raise ValueError("Koji install selected but neither koji_tag " | 1345 raise ValueError("Koji install selected but neither koji_tag " |
| 1516 "nor koji_build parameters provided. Please " | 1346 "nor koji_build parameters provided. Please " |
| 1517 "provide an appropriate tag or build name.") | 1347 "provide an appropriate tag or build name.") |
| 1518 | 1348 |
| 1519 if not build: | 1349 if not build: |
| 1520 builds = self.session.listTagged(tag, latest=True, inherit=True, | 1350 builds = self.session.listTagged(tag, latest=True, |
| 1521 package=src_package) | 1351 package=src_package) |
| 1522 if not builds: | 1352 if not builds: |
| 1523 raise ValueError("Tag %s has no builds of %s" % (tag, | 1353 raise ValueError("Tag %s has no builds of %s" % (tag, |
| 1524 src_package)) | 1354 src_package)) |
| 1525 info = builds[0] | 1355 info = builds[0] |
| 1526 else: | 1356 else: |
| 1527 info = self.session.getBuild(build) | 1357 info = self.session.getBuild(build) |
| 1528 | 1358 |
| 1529 if info is None: | 1359 if info is None: |
| 1530 raise ValueError('No such brew/koji build: %s' % build) | 1360 raise ValueError('No such brew/koji build: %s' % build) |
| (...skipping 22 matching lines...) Expand all Loading... |
| 1553 download = False | 1383 download = False |
| 1554 else: | 1384 else: |
| 1555 download = True | 1385 download = True |
| 1556 | 1386 |
| 1557 if download: | 1387 if download: |
| 1558 r = utils.get_file(url, | 1388 r = utils.get_file(url, |
| 1559 os.path.join(dst_dir, os.path.basename(url))) | 1389 os.path.join(dst_dir, os.path.basename(url))) |
| 1560 rpm_paths.append(r) | 1390 rpm_paths.append(r) |
| 1561 | 1391 |
| 1562 return rpm_paths | 1392 return rpm_paths |
| 1563 | |
| 1564 | |
| 1565 def umount(src, mount_point, type): | |
| 1566 """ | |
| 1567 Umount the src mounted in mount_point. | |
| 1568 | |
| 1569 @src: mount source | |
| 1570 @mount_point: mount point | |
| 1571 @type: file system type | |
| 1572 """ | |
| 1573 | |
| 1574 mount_string = "%s %s %s" % (src, mount_point, type) | |
| 1575 if mount_string in file("/etc/mtab").read(): | |
| 1576 umount_cmd = "umount %s" % mount_point | |
| 1577 try: | |
| 1578 utils.system(umount_cmd) | |
| 1579 return True | |
| 1580 except error.CmdError: | |
| 1581 return False | |
| 1582 else: | |
| 1583 logging.debug("%s is not mounted under %s" % (src, mount_point)) | |
| 1584 return True | |
| 1585 | |
| 1586 | |
| 1587 def mount(src, mount_point, type, perm="rw"): | |
| 1588 """ | |
| 1589 Mount the src into mount_point of the host. | |
| 1590 | |
| 1591 @src: mount source | |
| 1592 @mount_point: mount point | |
| 1593 @type: file system type | |
| 1594 @perm: mount premission | |
| 1595 """ | |
| 1596 umount(src, mount_point, type) | |
| 1597 mount_string = "%s %s %s %s" % (src, mount_point, type, perm) | |
| 1598 | |
| 1599 if mount_string in file("/etc/mtab").read(): | |
| 1600 logging.debug("%s is already mounted in %s with %s" % | |
| 1601 (src, mount_point, perm)) | |
| 1602 return True | |
| 1603 | |
| 1604 mount_cmd = "mount -t %s %s %s -o %s" % (type, src, mount_point, perm) | |
| 1605 try: | |
| 1606 utils.system(mount_cmd) | |
| 1607 except error.CmdError: | |
| 1608 return False | |
| 1609 | |
| 1610 logging.debug("Verify the mount through /etc/mtab") | |
| 1611 if mount_string in file("/etc/mtab").read(): | |
| 1612 logging.debug("%s is successfully mounted" % src) | |
| 1613 return True | |
| 1614 else: | |
| 1615 logging.error("Can't find mounted NFS share - /etc/mtab contents \n%s" % | |
| 1616 file("/etc/mtab").read()) | |
| 1617 return False | |
| OLD | NEW |