1 """ 2 KVM test utility functions. 3 4 @copyright: 2008-2009 Red Hat Inc. 5 """ 6 7 import time, string, random, socket, os, signal, re, logging, commands, cPickle 8 import fcntl, shelve, ConfigParser, threading, sys, UserDict, inspect, tarfile 9 import struct, shutil 10 from autotest_lib.client.bin import utils, os_dep 11 from autotest_lib.client.common_lib import error, logging_config 12 import rss_client, aexpect 13 try: 14 import koji 15 KOJI_INSTALLED = True 16 except ImportError: 17 KOJI_INSTALLED = False 18 19 # From include/linux/sockios.h 20 SIOCSIFHWADDR = 0x8924 21 SIOCGIFHWADDR = 0x8927 22 SIOCSIFFLAGS = 0x8914 23 SIOCGIFINDEX = 0x8933 24 SIOCBRADDIF = 0x89a2 25 # From linux/include/linux/if_tun.h 26 TUNSETIFF = 0x400454ca 27 TUNGETIFF = 0x800454d2 28 TUNGETFEATURES = 0x800454cf 29 IFF_UP = 0x1 30 IFF_TAP = 0x0002 31 IFF_NO_PI = 0x1000 32 IFF_VNET_HDR = 0x4000 33 34 def _lock_file(filename): 35 f = open(filename, "w") 36 fcntl.lockf(f, fcntl.LOCK_EX) 37 return f 38 39 40 def _unlock_file(f): 41 fcntl.lockf(f, fcntl.LOCK_UN) 42 f.close() 43 44 45 def is_vm(obj): 46 """ 47 Tests whether a given object is a VM object. 48 49 @param obj: Python object. 50 """ 51 return obj.__class__.__name__ == "VM" 52 53 54 class NetError(Exception): 55 pass 56 57 58 class TAPModuleError(NetError): 59 def __init__(self, devname, action="open", details=None): 60 NetError.__init__(self, devname) 61 self.devname = devname 62 self.details = details 63 64 def __str__(self): 65 e_msg = "Can't %s %s" % (self.action, self.devname) 66 if self.details is not None: 67 e_msg += " : %s" % self.details 68 return e_msg 69 70 71 class TAPNotExistError(NetError): 72 def __init__(self, ifname): 73 NetError.__init__(self, ifname) 74 self.ifname = ifname 75 76 def __str__(self): 77 return "Interface %s does not exist" % self.ifname 78 79 80 class TAPCreationError(NetError): 81 def __init__(self, ifname, details=None): 82 NetError.__init__(self, ifname, details) 83 self.ifname = ifname 84 self.details = details 85 86 def __str__(self): 87 e_msg = "Cannot create TAP device %s" % self.ifname 88 if self.details is not None: 89 e_msg += ": %s" % self.details 90 return e_msg 91 92 93 class TAPBringUpError(NetError): 94 def __init__(self, ifname): 95 NetError.__init__(self, ifname) 96 self.ifname = ifname 97 98 def __str__(self): 99 return "Cannot bring up TAP %s" % self.ifname 100 101 102 class BRAddIfError(NetError): 103 def __init__(self, ifname, brname, details): 104 NetError.__init__(self, ifname, brname, details) 105 self.ifname = ifname 106 self.brname = brname 107 self.details = details 108 109 def __str__(self): 110 return ("Can not add if %s to bridge %s: %s" % 111 (self.ifname, self.brname, self.details)) 112 113 114 class HwAddrSetError(NetError): 115 def __init__(self, ifname, mac): 116 NetError.__init__(self, ifname, mac) 117 self.ifname = ifname 118 self.mac = mac 119 120 def __str__(self): 121 return "Can not set mac %s to interface %s" % (self.mac, self.ifname) 122 123 124 class HwAddrGetError(NetError): 125 def __init__(self, ifname): 126 NetError.__init__(self, ifname) 127 self.ifname = ifname 128 129 def __str__(self): 130 return "Can not get mac of interface %s" % self.ifname 131 132 133 class Env(UserDict.IterableUserDict): 134 """ 135 A dict-like object containing global objects used by tests. 136 """ 137 def __init__(self, filename=None, version=0): 138 """ 139 Create an empty Env object or load an existing one from a file. 140 141 If the version recorded in the file is lower than version, or if some 142 error occurs during unpickling, or if filename is not supplied, 143 create an empty Env object. 144 145 @param filename: Path to an env file. 146 @param version: Required env version (int). 147 """ 148 UserDict.IterableUserDict.__init__(self) 149 empty = {"version": version} 150 if filename: 151 self._filename = filename 152 try: 153 if os.path.isfile(filename): 154 f = open(filename, "r") 155 env = cPickle.load(f) 156 f.close() 157 if env.get("version", 0) >= version: 158 self.data = env 159 else: 160 logging.warning("Incompatible env file found. Not using it.") 161 self.data = empty 162 else: 163 # No previous env file found, proceed... 164 self.data = empty 165 # Almost any exception can be raised during unpickling, so let's 166 # catch them all 167 except Exception, e: 168 logging.warning(e) 169 self.data = empty 170 else: 171 self.data = empty 172 173 174 def save(self, filename=None): 175 """ 176 Pickle the contents of the Env object into a file. 177 178 @param filename: Filename to pickle the dict into. If not supplied, 179 use the filename from which the dict was loaded. 180 """ 181 filename = filename or self._filename 182 f = open(filename, "w") 183 cPickle.dump(self.data, f) 184 f.close() 185 186 187 def get_all_vms(self): 188 """ 189 Return a list of all VM objects in this Env object. 190 """ 191 return [o for o in self.values() if is_vm(o)] 192 193 194 def get_vm(self, name): 195 """ 196 Return a VM object by its name. 197 198 @param name: VM name. 199 """ 200 return self.get("vm__%s" % name) 201 202 203 def register_vm(self, name, vm): 204 """ 205 Register a VM in this Env object. 206 207 @param name: VM name. 208 @param vm: VM object. 209 """ 210 self["vm__%s" % name] = vm 211 212 213 def unregister_vm(self, name): 214 """ 215 Remove a given VM. 216 217 @param name: VM name. 218 """ 219 del self["vm__%s" % name] 220 221 222 def register_installer(self, installer): 223 """ 224 Register a installer that was just run 225 226 The installer will be available for other tests, so that 227 information about the installed KVM modules and qemu-kvm can be used by 228 them. 229 """ 230 self['last_installer'] = installer 231 232 233 def previous_installer(self): 234 """ 235 Return the last installer that was registered 236 """ 237 return self.get('last_installer') 238 239 240 class Params(UserDict.IterableUserDict): 241 """ 242 A dict-like object passed to every test. 243 """ 244 def objects(self, key): 245 """ 246 Return the names of objects defined using a given key. 247 248 @param key: The name of the key whose value lists the objects 249 (e.g. 'nics'). 250 """ 251 return self.get(key, "").split() 252 253 254 def object_params(self, obj_name): 255 """ 256 Return a dict-like object containing the parameters of an individual 257 object. 258 259 This method behaves as follows: the suffix '_' + obj_name is removed 260 from all key names that have it. Other key names are left unchanged. 261 The values of keys with the suffix overwrite the values of their 262 suffixless versions. 263 264 @param obj_name: The name of the object (objects are listed by the 265 objects() method). 266 """ 267 suffix = "_" + obj_name 268 new_dict = self.copy() 269 for key in self: 270 if key.endswith(suffix): 271 new_key = key.split(suffix)[0] 272 new_dict[new_key] = self[key] 273 return new_dict 274 275 276 # Functions related to MAC/IP addresses 277 278 def _open_mac_pool(lock_mode): 279 lock_file = open("/tmp/mac_lock", "w+") 280 fcntl.lockf(lock_file, lock_mode) 281 pool = shelve.open("/tmp/address_pool") 282 return pool, lock_file 283 284 285 def _close_mac_pool(pool, lock_file): 286 pool.close() 287 fcntl.lockf(lock_file, fcntl.LOCK_UN) 288 lock_file.close() 289 290 291 def _generate_mac_address_prefix(mac_pool): 292 """ 293 Generate a random MAC address prefix and add it to the MAC pool dictionary. 294 If there's a MAC prefix there already, do not update the MAC pool and just 295 return what's in there. By convention we will set KVM autotest MAC 296 addresses to start with 0x9a. 297 298 @param mac_pool: The MAC address pool object. 299 @return: The MAC address prefix. 300 """ 301 if "prefix" in mac_pool: 302 prefix = mac_pool["prefix"] 303 else: 304 r = random.SystemRandom() 305 prefix = "9a:%02x:%02x:%02x:" % (r.randint(0x00, 0xff), 306 r.randint(0x00, 0xff), 307 r.randint(0x00, 0xff)) 308 mac_pool["prefix"] = prefix 309 return prefix 310 311 312 def generate_mac_address(vm_instance, nic_index): 313 """ 314 Randomly generate a MAC address and add it to the MAC address pool. 315 316 Try to generate a MAC address based on a randomly generated MAC address 317 prefix and add it to a persistent dictionary. 318 key = VM instance + NIC index, value = MAC address 319 e.g. {'20100310-165222-Wt7l:0': '9a:5d:94:6a:9b:f9'} 320 321 @param vm_instance: The instance attribute of a VM. 322 @param nic_index: The index of the NIC. 323 @return: MAC address string. 324 """ 325 mac_pool, lock_file = _open_mac_pool(fcntl.LOCK_EX) 326 key = "%s:%s" % (vm_instance, nic_index) 327 if key in mac_pool: 328 mac = mac_pool[key] 329 else: 330 prefix = _generate_mac_address_prefix(mac_pool) 331 r = random.SystemRandom() 332 while key not in mac_pool: 333 mac = prefix + "%02x:%02x" % (r.randint(0x00, 0xff), 334 r.randint(0x00, 0xff)) 335 if mac in mac_pool.values(): 336 continue 337 mac_pool[key] = mac 338 _close_mac_pool(mac_pool, lock_file) 339 return mac 340 341 342 def free_mac_address(vm_instance, nic_index): 343 """ 344 Remove a MAC address from the address pool. 345 346 @param vm_instance: The instance attribute of a VM. 347 @param nic_index: The index of the NIC. 348 """ 349 mac_pool, lock_file = _open_mac_pool(fcntl.LOCK_EX) 350 key = "%s:%s" % (vm_instance, nic_index) 351 if key in mac_pool: 352 del mac_pool[key] 353 _close_mac_pool(mac_pool, lock_file) 354 355 356 def set_mac_address(vm_instance, nic_index, mac): 357 """ 358 Set a MAC address in the pool. 359 360 @param vm_instance: The instance attribute of a VM. 361 @param nic_index: The index of the NIC. 362 """ 363 mac_pool, lock_file = _open_mac_pool(fcntl.LOCK_EX) 364 mac_pool["%s:%s" % (vm_instance, nic_index)] = mac 365 _close_mac_pool(mac_pool, lock_file) 366 367 368 def get_mac_address(vm_instance, nic_index): 369 """ 370 Return a MAC address from the pool. 371 372 @param vm_instance: The instance attribute of a VM. 373 @param nic_index: The index of the NIC. 374 @return: MAC address string. 375 """ 376 mac_pool, lock_file = _open_mac_pool(fcntl.LOCK_SH) 377 mac = mac_pool.get("%s:%s" % (vm_instance, nic_index)) 378 _close_mac_pool(mac_pool, lock_file) 379 return mac 380 381 382 def verify_ip_address_ownership(ip, macs, timeout=10.0): 383 """ 384 Use arping and the ARP cache to make sure a given IP address belongs to one 385 of the given MAC addresses. 386 387 @param ip: An IP address. 388 @param macs: A list or tuple of MAC addresses. 389 @return: True iff ip is assigned to a MAC address in macs. 390 """ 391 # Compile a regex that matches the given IP address and any of the given 392 # MAC addresses 393 mac_regex = "|".join("(%s)" % mac for mac in macs) 394 regex = re.compile(r"\b%s\b.*\b(%s)\b" % (ip, mac_regex), re.IGNORECASE) 395 396 # Check the ARP cache 397 o = commands.getoutput("%s -n" % find_command("arp")) 398 if regex.search(o): 399 return True 400 401 # Get the name of the bridge device for arping 402 o = commands.getoutput("%s route get %s" % (find_command("ip"), ip)) 403 dev = re.findall("dev\s+\S+", o, re.IGNORECASE) 404 if not dev: 405 return False 406 dev = dev[0].split()[-1] 407 408 # Send an ARP request 409 o = commands.getoutput("%s -f -c 3 -I %s %s" % 410 (find_command("arping"), dev, ip)) 411 return bool(regex.search(o)) 412 413 414 # Utility functions for dealing with external processes 415 416 def find_command(cmd): 417 for dir in ["/usr/local/sbin", "/usr/local/bin", 418 "/usr/sbin", "/usr/bin", "/sbin", "/bin"]: 419 file = os.path.join(dir, cmd) 420 if os.path.exists(file): 421 return file 422 raise ValueError('Missing command: %s' % cmd) 423 424 425 def pid_exists(pid): 426 """ 427 Return True if a given PID exists. 428 429 @param pid: Process ID number. 430 """ 431 try: 432 os.kill(pid, 0) 433 return True 434 except: 435 return False 436 437 438 def safe_kill(pid, signal): 439 """ 440 Attempt to send a signal to a given process that may or may not exist. 441 442 @param signal: Signal number. 443 """ 444 try: 445 os.kill(pid, signal) 446 return True 447 except: 448 return False 449 450 451 def kill_process_tree(pid, sig=signal.SIGKILL): 452 """Signal a process and all of its children. 453 454 If the process does not exist -- return. 455 456 @param pid: The pid of the process to signal. 457 @param sig: The signal to send to the processes. 458 """ 459 if not safe_kill(pid, signal.SIGSTOP): 460 return 461 children = commands.getoutput("ps --ppid=%d -o pid=" % pid).split() 462 for child in children: 463 kill_process_tree(int(child), sig) 464 safe_kill(pid, sig) 465 safe_kill(pid, signal.SIGCONT) 466 467 468 def get_git_branch(repository, branch, srcdir, commit=None, lbranch=None): 469 """ 470 Retrieves a given git code repository. 471 472 @param repository: Git repository URL 473 """ 474 logging.info("Fetching git [REP '%s' BRANCH '%s' COMMIT '%s'] -> %s", 475 repository, branch, commit, srcdir) 476 if not os.path.exists(srcdir): 477 os.makedirs(srcdir) 478 os.chdir(srcdir) 479 480 if os.path.exists(".git"): 481 utils.system("git reset --hard") 482 else: 483 utils.system("git init") 484 485 if not lbranch: 486 lbranch = branch 487 488 utils.system("git fetch -q -f -u -t %s %s:%s" % 489 (repository, branch, lbranch)) 490 utils.system("git checkout %s" % lbranch) 491 if commit: 492 utils.system("git checkout %s" % commit) 493 494 h = utils.system_output('git log --pretty=format:"%H" -1') 495 try: 496 desc = "tag %s" % utils.system_output("git describe") 497 except error.CmdError: 498 desc = "no tag found" 499 500 logging.info("Commit hash for %s is %s (%s)", repository, h.strip(), desc) 501 return srcdir 502 503 504 def check_kvm_source_dir(source_dir): 505 """ 506 Inspects the kvm source directory and verifies its disposition. In some 507 occasions build may be dependant on the source directory disposition. 508 The reason why the return codes are numbers is that we might have more 509 changes on the source directory layout, so it's not scalable to just use 510 strings like 'old_repo', 'new_repo' and such. 511 512 @param source_dir: Source code path that will be inspected. 513 """ 514 os.chdir(source_dir) 515 has_qemu_dir = os.path.isdir('qemu') 516 has_kvm_dir = os.path.isdir('kvm') 517 if has_qemu_dir: 518 logging.debug("qemu directory detected, source dir layout 1") 519 return 1 520 if has_kvm_dir and not has_qemu_dir: 521 logging.debug("kvm directory detected, source dir layout 2") 522 return 2 523 else: 524 raise error.TestError("Unknown source dir layout, cannot proceed.") 525 526 527 # Functions and classes used for logging into guests and transferring files 528 529 class LoginError(Exception): 530 def __init__(self, msg, output): 531 Exception.__init__(self, msg, output) 532 self.msg = msg 533 self.output = output 534 535 def __str__(self): 536 return "%s (output: %r)" % (self.msg, self.output) 537 538 539 class LoginAuthenticationError(LoginError): 540 pass 541 542 543 class LoginTimeoutError(LoginError): 544 def __init__(self, output): 545 LoginError.__init__(self, "Login timeout expired", output) 546 547 548 class LoginProcessTerminatedError(LoginError): 549 def __init__(self, status, output): 550 LoginError.__init__(self, None, output) 551 self.status = status 552 553 def __str__(self): 554 return ("Client process terminated (status: %s, output: %r)" % 555 (self.status, self.output)) 556 557 558 class LoginBadClientError(LoginError): 559 def __init__(self, client): 560 LoginError.__init__(self, None, None) 561 self.client = client 562 563 def __str__(self): 564 return "Unknown remote shell client: %r" % self.client 565 566 567 class SCPError(Exception): 568 def __init__(self, msg, output): 569 Exception.__init__(self, msg, output) 570 self.msg = msg 571 self.output = output 572 573 def __str__(self): 574 return "%s (output: %r)" % (self.msg, self.output) 575 576 577 class SCPAuthenticationError(SCPError): 578 pass 579 580 581 class SCPAuthenticationTimeoutError(SCPAuthenticationError): 582 def __init__(self, output): 583 SCPAuthenticationError.__init__(self, "Authentication timeout expired", 584 output) 585 586 587 class SCPTransferTimeoutError(SCPError): 588 def __init__(self, output): 589 SCPError.__init__(self, "Transfer timeout expired", output) 590 591 592 class SCPTransferFailedError(SCPError): 593 def __init__(self, status, output): 594 SCPError.__init__(self, None, output) 595 self.status = status 596 597 def __str__(self): 598 return ("SCP transfer failed (status: %s, output: %r)" % 599 (self.status, self.output)) 600 601 602 def _remote_login(session, username, password, prompt, timeout=10): 603 """ 604 Log into a remote host (guest) using SSH or Telnet. Wait for questions 605 and provide answers. If timeout expires while waiting for output from the 606 child (e.g. a password prompt or a shell prompt) -- fail. 607 608 @brief: Log into a remote host (guest) using SSH or Telnet. 609 610 @param session: An Expect or ShellSession instance to operate on 611 @param username: The username to send in reply to a login prompt 612 @param password: The password to send in reply to a password prompt 613 @param prompt: The shell prompt that indicates a successful login 614 @param timeout: The maximal time duration (in seconds) to wait for each 615 step of the login procedure (i.e. the "Are you sure" prompt, the 616 password prompt, the shell prompt, etc) 617 @raise LoginTimeoutError: If timeout expires 618 @raise LoginAuthenticationError: If authentication fails 619 @raise LoginProcessTerminatedError: If the client terminates during login 620 @raise LoginError: If some other error occurs 621 """ 622 password_prompt_count = 0 623 login_prompt_count = 0 624 625 while True: 626 try: 627 match, text = session.read_until_last_line_matches( 628 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"[Ll]ogin:\s*$", 629 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", 630 r"[Pp]lease wait", r"[Ww]arning", prompt], 631 timeout=timeout, internal_timeout=0.5) 632 if match == 0: # "Are you sure you want to continue connecting" 633 logging.debug("Got 'Are you sure...', sending 'yes'") 634 session.sendline("yes") 635 continue 636 elif match == 1: # "password:" 637 if password_prompt_count == 0: 638 logging.debug("Got password prompt, sending '%s'", password) 639 session.sendline(password) 640 password_prompt_count += 1 641 continue 642 else: 643 raise LoginAuthenticationError("Got password prompt twice", 644 text) 645 elif match == 2: # "login:" 646 if login_prompt_count == 0 and password_prompt_count == 0: 647 logging.debug("Got username prompt; sending '%s'", username) 648 session.sendline(username) 649 login_prompt_count += 1 650 continue 651 else: 652 if login_prompt_count > 0: 653 msg = "Got username prompt twice" 654 else: 655 msg = "Got username prompt after password prompt" 656 raise LoginAuthenticationError(msg, text) 657 elif match == 3: # "Connection closed" 658 raise LoginError("Client said 'connection closed'", text) 659 elif match == 4: # "Connection refused" 660 raise LoginError("Client said 'connection refused'", text) 661 elif match == 5: # "Please wait" 662 logging.debug("Got 'Please wait'") 663 timeout = 30 664 continue 665 elif match == 6: # "Warning added RSA" 666 logging.debug("Got 'Warning added RSA to known host list") 667 continue 668 elif match == 7: # prompt 669 logging.debug("Got shell prompt -- logged in") 670 break 671 except aexpect.ExpectTimeoutError, e: 672 raise LoginTimeoutError(e.output) 673 except aexpect.ExpectProcessTerminatedError, e: 674 raise LoginProcessTerminatedError(e.status, e.output) 675 676 677 def remote_login(client, host, port, username, password, prompt, linesep="\n", 678 log_filename=None, timeout=10): 679 """ 680 Log into a remote host (guest) using SSH/Telnet/Netcat. 681 682 @param client: The client to use ('ssh', 'telnet' or 'nc') 683 @param host: Hostname or IP address 684 @param port: Port to connect to 685 @param username: Username (if required) 686 @param password: Password (if required) 687 @param prompt: Shell prompt (regular expression) 688 @param linesep: The line separator to use when sending lines 689 (e.g. '\\n' or '\\r\\n') 690 @param log_filename: If specified, log all output to this file 691 @param timeout: The maximal time duration (in seconds) to wait for 692 each step of the login procedure (i.e. the "Are you sure" prompt 693 or the password prompt) 694 @raise LoginBadClientError: If an unknown client is requested 695 @raise: Whatever _remote_login() raises 696 @return: A ShellSession object. 697 """ 698 if client == "ssh": 699 cmd = ("ssh -o UserKnownHostsFile=/dev/null " 700 "-o PreferredAuthentications=password -p %s %s@%s" % 701 (port, username, host)) 702 elif client == "telnet": 703 cmd = "telnet -l %s %s %s" % (username, host, port) 704 elif client == "nc": 705 cmd = "nc %s %s" % (host, port) 706 else: 707 raise LoginBadClientError(client) 708 709 logging.debug("Trying to login with command '%s'", cmd) 710 session = aexpect.ShellSession(cmd, linesep=linesep, prompt=prompt) 711 try: 712 _remote_login(session, username, password, prompt, timeout) 713 except: 714 session.close() 715 raise 716 if log_filename: 717 session.set_output_func(log_line) 718 session.set_output_params((log_filename,)) 719 return session 720 721 722 def wait_for_login(client, host, port, username, password, prompt, linesep="\n", 723 log_filename=None, timeout=240, internal_timeout=10): 724 """ 725 Make multiple attempts to log into a remote host (guest) until one succeeds 726 or timeout expires. 727 728 @param timeout: Total time duration to wait for a successful login 729 @param internal_timeout: The maximal time duration (in seconds) to wait for 730 each step of the login procedure (e.g. the "Are you sure" prompt 731 or the password prompt) 732 @see: remote_login() 733 @raise: Whatever remote_login() raises 734 @return: A ShellSession object. 735 """ 736 logging.debug("Attempting to log into %s:%s using %s (timeout %ds)", 737 host, port, client, timeout) 738 end_time = time.time() + timeout 739 while time.time() < end_time: 740 try: 741 return remote_login(client, host, port, username, password, prompt, 742 linesep, log_filename, internal_timeout) 743 except LoginError, e: 744 logging.debug(e) 745 time.sleep(2) 746 # Timeout expired; try one more time but don't catch exceptions 747 return remote_login(client, host, port, username, password, prompt, 748 linesep, log_filename, internal_timeout) 749 750 751 def _remote_scp(session, password_list, transfer_timeout=600, login_timeout=10): 752 """ 753 Transfer file(s) to a remote host (guest) using SCP. Wait for questions 754 and provide answers. If login_timeout expires while waiting for output 755 from the child (e.g. a password prompt), fail. If transfer_timeout expires 756 while waiting for the transfer to complete, fail. 757 758 @brief: Transfer files using SCP, given a command line. 759 760 @param session: An Expect or ShellSession instance to operate on 761 @param password_list: Password list to send in reply to the password prompt 762 @param transfer_timeout: The time duration (in seconds) to wait for the 763 transfer to complete. 764 @param login_timeout: The maximal time duration (in seconds) to wait for 765 each step of the login procedure (i.e. the "Are you sure" prompt or 766 the password prompt) 767 @raise SCPAuthenticationError: If authentication fails 768 @raise SCPTransferTimeoutError: If the transfer fails to complete in time 769 @raise SCPTransferFailedError: If the process terminates with a nonzero 770 exit code 771 @raise SCPError: If some other error occurs 772 """ 773 password_prompt_count = 0 774 timeout = login_timeout 775 authentication_done = False 776 777 scp_type = len(password_list) 778 779 while True: 780 try: 781 match, text = session.read_until_last_line_matches( 782 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"], 783 timeout=timeout, internal_timeout=0.5) 784 if match == 0: # "Are you sure you want to continue connecting" 785 logging.debug("Got 'Are you sure...', sending 'yes'") 786 session.sendline("yes") 787 continue 788 elif match == 1: # "password:" 789 if password_prompt_count == 0: 790 logging.debug("Got password prompt, sending '%s'" % 791 password_list[password_prompt_count]) 792 session.sendline(password_list[password_prompt_count]) 793 password_prompt_count += 1 794 timeout = transfer_timeout 795 if scp_type == 1: 796 authentication_done = True 797 continue 798 elif password_prompt_count == 1 and scp_type == 2: 799 logging.debug("Got password prompt, sending '%s'" % 800 password_list[password_prompt_count]) 801 session.sendline(password_list[password_prompt_count]) 802 password_prompt_count += 1 803 timeout = transfer_timeout 804 authentication_done = True 805 continue 806 else: 807 raise SCPAuthenticationError("Got password prompt twice", 808 text) 809 elif match == 2: # "lost connection" 810 raise SCPError("SCP client said 'lost connection'", text) 811 except aexpect.ExpectTimeoutError, e: 812 if authentication_done: 813 raise SCPTransferTimeoutError(e.output) 814 else: 815 raise SCPAuthenticationTimeoutError(e.output) 816 except aexpect.ExpectProcessTerminatedError, e: 817 if e.status == 0: 818 logging.debug("SCP process terminated with status 0") 819 break 820 else: 821 raise SCPTransferFailedError(e.status, e.output) 822 823 824 def remote_scp(command, password_list, log_filename=None, transfer_timeout=600, 825 login_timeout=10): 826 """ 827 Transfer file(s) to a remote host (guest) using SCP. 828 829 @brief: Transfer files using SCP, given a command line. 830 831 @param command: The command to execute 832 (e.g. "scp -r foobar root@localhost:/tmp/"). 833 @param password_list: Password list to send in reply to a password prompt. 834 @param log_filename: If specified, log all output to this file 835 @param transfer_timeout: The time duration (in seconds) to wait for the 836 transfer to complete. 837 @param login_timeout: The maximal time duration (in seconds) to wait for 838 each step of the login procedure (i.e. the "Are you sure" prompt 839 or the password prompt) 840 @raise: Whatever _remote_scp() raises 841 """ 842 logging.debug("Trying to SCP with command '%s', timeout %ss", 843 command, transfer_timeout) 844 if log_filename: 845 output_func = log_line 846 output_params = (log_filename,) 847 else: 848 output_func = None 849 output_params = () 850 session = aexpect.Expect(command, 851 output_func=output_func, 852 output_params=output_params) 853 try: 854 _remote_scp(session, password_list, transfer_timeout, login_timeout) 855 finally: 856 session.close() 857 858 859 def scp_to_remote(host, port, username, password, local_path, remote_path, 860 log_filename=None, timeout=600): 861 """ 862 Copy files to a remote host (guest) through scp. 863 864 @param host: Hostname or IP address 865 @param username: Username (if required) 866 @param password: Password (if required) 867 @param local_path: Path on the local machine where we are copying from 868 @param remote_path: Path on the remote machine where we are copying to 869 @param log_filename: If specified, log all output to this file 870 @param timeout: The time duration (in seconds) to wait for the transfer 871 to complete. 872 @raise: Whatever remote_scp() raises 873 """ 874 command = ("scp -v -o UserKnownHostsFile=/dev/null " 875 "-o PreferredAuthentications=password -r -P %s %s %s@%s:%s" % 876 (port, local_path, username, host, remote_path)) 877 password_list = [] 878 password_list.append(password) 879 return remote_scp(command, password_list, log_filename, timeout) 880 881 882 883 def scp_from_remote(host, port, username, password, remote_path, local_path, 884 log_filename=None, timeout=600): 885 """ 886 Copy files from a remote host (guest). 887 888 @param host: Hostname or IP address 889 @param username: Username (if required) 890 @param password: Password (if required) 891 @param local_path: Path on the local machine where we are copying from 892 @param remote_path: Path on the remote machine where we are copying to 893 @param log_filename: If specified, log all output to this file 894 @param timeout: The time duration (in seconds) to wait for the transfer 895 to complete. 896 @raise: Whatever remote_scp() raises 897 """ 898 command = ("scp -v -o UserKnownHostsFile=/dev/null " 899 "-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" % 900 (port, username, host, remote_path, local_path)) 901 password_list = [] 902 password_list.append(password) 903 remote_scp(command, password_list, log_filename, timeout) 904 905 906 def scp_between_remotes(src, dst, port, s_passwd, d_passwd, s_name, d_name, 907 s_path, d_path, log_filename=None, timeout=600): 908 """ 909 Copy files from a remote host (guest) to another remote host (guest). 910 911 @param src/dst: Hostname or IP address of src and dst 912 @param s_name/d_name: Username (if required) 913 @param s_passwd/d_passwd: Password (if required) 914 @param s_path/d_path: Path on the remote machine where we are copying 915 from/to 916 @param log_filename: If specified, log all output to this file 917 @param timeout: The time duration (in seconds) to wait for the transfer 918 to complete. 919 920 @return: True on success and False on failure. 921 """ 922 command = ("scp -v -o UserKnownHostsFile=/dev/null -o " 923 "PreferredAuthentications=password -r -P %s %s@%s:%s %s@%s:%s" % 924 (port, s_name, src, s_path, d_name, dst, d_path)) 925 password_list = [] 926 password_list.append(s_passwd) 927 password_list.append(d_passwd) 928 return remote_scp(command, password_list, log_filename, timeout) 929 930 931 def copy_files_to(address, client, username, password, port, local_path, 932 remote_path, log_filename=None, verbose=False, timeout=600): 933 """ 934 Copy files to a remote host (guest) using the selected client. 935 936 @param client: Type of transfer client 937 @param username: Username (if required) 938 @param password: Password (if requried) 939 @param local_path: Path on the local machine where we are copying from 940 @param remote_path: Path on the remote machine where we are copying to 941 @param address: Address of remote host(guest) 942 @param log_filename: If specified, log all output to this file (SCP only) 943 @param verbose: If True, log some stats using logging.debug (RSS only) 944 @param timeout: The time duration (in seconds) to wait for the transfer to 945 complete. 946 @raise: Whatever remote_scp() raises 947 """ 948 if client == "scp": 949 scp_to_remote(address, port, username, password, local_path, 950 remote_path, log_filename, timeout) 951 elif client == "rss": 952 log_func = None 953 if verbose: 954 log_func = logging.debug 955 c = rss_client.FileUploadClient(address, port, log_func) 956 c.upload(local_path, remote_path, timeout) 957 c.close() 958 959 960 def copy_files_from(address, client, username, password, port, remote_path, 961 local_path, log_filename=None, verbose=False, timeout=600): 962 """ 963 Copy files from a remote host (guest) using the selected client. 964 965 @param client: Type of transfer client 966 @param username: Username (if required) 967 @param password: Password (if requried) 968 @param remote_path: Path on the remote machine where we are copying from 969 @param local_path: Path on the local machine where we are copying to 970 @param address: Address of remote host(guest) 971 @param log_filename: If specified, log all output to this file (SCP only) 972 @param verbose: If True, log some stats using logging.debug (RSS only) 973 @param timeout: The time duration (in seconds) to wait for the transfer to 974 complete. 975 @raise: Whatever remote_scp() raises 976 """ 977 if client == "scp": 978 scp_from_remote(address, port, username, password, remote_path, 979 local_path, log_filename, timeout) 980 elif client == "rss": 981 log_func = None 982 if verbose: 983 log_func = logging.debug 984 c = rss_client.FileDownloadClient(address, port, log_func) 985 c.download(remote_path, local_path, timeout) 986 c.close() 987 988 989 # The following are utility functions related to ports. 990 991 def is_port_free(port, address): 992 """ 993 Return True if the given port is available for use. 994 995 @param port: Port number 996 """ 997 try: 998 s = socket.socket() 999 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 1000 if address == "localhost": 1001 s.bind(("localhost", port)) 1002 free = True 1003 else: 1004 s.connect((address, port)) 1005 free = False 1006 except socket.error: 1007 if address == "localhost": 1008 free = False 1009 else: 1010 free = True 1011 s.close() 1012 return free 1013 1014 1015 def find_free_port(start_port, end_port, address="localhost"): 1016 """ 1017 Return a host free port in the range [start_port, end_port]. 1018 1019 @param start_port: First port that will be checked. 1020 @param end_port: Port immediately after the last one that will be checked. 1021 """ 1022 for i in range(start_port, end_port): 1023 if is_port_free(i, address): 1024 return i 1025 return None 1026 1027 1028 def find_free_ports(start_port, end_port, count, address="localhost"): 1029 """ 1030 Return count of host free ports in the range [start_port, end_port]. 1031 1032 @count: Initial number of ports known to be free in the range. 1033 @param start_port: First port that will be checked. 1034 @param end_port: Port immediately after the last one that will be checked. 1035 """ 1036 ports = [] 1037 i = start_port 1038 while i < end_port and count > 0: 1039 if is_port_free(i, address): 1040 ports.append(i) 1041 count -= 1 1042 i += 1 1043 return ports 1044 1045 1046 # An easy way to log lines to files when the logging system can't be used 1047 1048 _open_log_files = {} 1049 _log_file_dir = "/tmp" 1050 1051 1052 def log_line(filename, line): 1053 """ 1054 Write a line to a file. '\n' is appended to the line. 1055 1056 @param filename: Path of file to write to, either absolute or relative to 1057 the dir set by set_log_file_dir(). 1058 @param line: Line to write. 1059 """ 1060 global _open_log_files, _log_file_dir 1061 if filename not in _open_log_files: 1062 path = get_path(_log_file_dir, filename) 1063 try: 1064 os.makedirs(os.path.dirname(path)) 1065 except OSError: 1066 pass 1067 _open_log_files[filename] = open(path, "w") 1068 timestr = time.strftime("%Y-%m-%d %H:%M:%S") 1069 _open_log_files[filename].write("%s: %s\n" % (timestr, line)) 1070 _open_log_files[filename].flush() 1071 1072 1073 def set_log_file_dir(dir): 1074 """ 1075 Set the base directory for log files created by log_line(). 1076 1077 @param dir: Directory for log files. 1078 """ 1079 global _log_file_dir 1080 _log_file_dir = dir 1081 1082 1083 # The following are miscellaneous utility functions. 1084 1085 def get_path(base_path, user_path): 1086 """ 1087 Translate a user specified path to a real path. 1088 If user_path is relative, append it to base_path. 1089 If user_path is absolute, return it as is. 1090 1091 @param base_path: The base path of relative user specified paths. 1092 @param user_path: The user specified path. 1093 """ 1094 if os.path.isabs(user_path): 1095 return user_path 1096 else: 1097 return os.path.join(base_path, user_path) 1098 1099 1100 def generate_random_string(length): 1101 """ 1102 Return a random string using alphanumeric characters. 1103 1104 @length: length of the string that will be generated. 1105 """ 1106 r = random.SystemRandom() 1107 str = "" 1108 chars = string.letters + string.digits 1109 while length > 0: 1110 str += r.choice(chars) 1111 length -= 1 1112 return str 1113 1114 def generate_random_id(): 1115 """ 1116 Return a random string suitable for use as a qemu id. 1117 """ 1118 return "id" + generate_random_string(6) 1119 1120 1121 def generate_tmp_file_name(file, ext=None, dir='/tmp/'): 1122 """ 1123 Returns a temporary file name. The file is not created. 1124 """ 1125 while True: 1126 file_name = (file + '-' + time.strftime("%Y%m%d-%H%M%S-") + 1127 generate_random_string(4)) 1128 if ext: 1129 file_name += '.' + ext 1130 file_name = os.path.join(dir, file_name) 1131 if not os.path.exists(file_name): 1132 break 1133 1134 return file_name 1135 1136 1137 def format_str_for_message(str): 1138 """ 1139 Format str so that it can be appended to a message. 1140 If str consists of one line, prefix it with a space. 1141 If str consists of multiple lines, prefix it with a newline. 1142 1143 @param str: string that will be formatted. 1144 """ 1145 lines = str.splitlines() 1146 num_lines = len(lines) 1147 str = "\n".join(lines) 1148 if num_lines == 0: 1149 return "" 1150 elif num_lines == 1: 1151 return " " + str 1152 else: 1153 return "\n" + str 1154 1155 1156 def wait_for(func, timeout, first=0.0, step=1.0, text=None): 1157 """ 1158 If func() evaluates to True before timeout expires, return the 1159 value of func(). Otherwise return None. 1160 1161 @brief: Wait until func() evaluates to True. 1162 1163 @param timeout: Timeout in seconds 1164 @param first: Time to sleep before first attempt 1165 @param steps: Time to sleep between attempts in seconds 1166 @param text: Text to print while waiting, for debug purposes 1167 """ 1168 start_time = time.time() 1169 end_time = time.time() + timeout 1170 1171 time.sleep(first) 1172 1173 while time.time() < end_time: 1174 if text: 1175 logging.debug("%s (%f secs)", text, (time.time() - start_time)) 1176 1177 output = func() 1178 if output: 1179 return output 1180 1181 time.sleep(step) 1182 1183 return None 1184 1185 1186 def get_hash_from_file(hash_path, dvd_basename): 1187 """ 1188 Get the a hash from a given DVD image from a hash file 1189 (Hash files are usually named MD5SUM or SHA1SUM and are located inside the 1190 download directories of the DVDs) 1191 1192 @param hash_path: Local path to a hash file. 1193 @param cd_image: Basename of a CD image 1194 """ 1195 hash_file = open(hash_path, 'r') 1196 for line in hash_file.readlines(): 1197 if dvd_basename in line: 1198 return line.split()[0] 1199 1200 1201 def run_tests(parser, job): 1202 """ 1203 Runs the sequence of KVM tests based on the list of dictionaries 1204 generated by the configuration system, handling dependencies. 1205 1206 @param parser: Config parser object. 1207 @param job: Autotest job object. 1208 1209 @return: True, if all tests ran passed, False if any of them failed. 1210 """ 1211 for i, d in enumerate(parser.get_dicts()): 1212 logging.info("Test %4d: %s" % (i + 1, d["shortname"])) 1213 1214 status_dict = {} 1215 failed = False 1216 1217 for dict in parser.get_dicts(): 1218 if dict.get("skip") == "yes": 1219 continue 1220 dependencies_satisfied = True 1221 for dep in dict.get("dep"): 1222 for test_name in status_dict.keys(): 1223 if not dep in test_name: 1224 continue 1225 # So the only really non-fatal state is WARN, 1226 # All the others make it not safe to proceed with dependency 1227 # execution 1228 if status_dict[test_name] not in ['GOOD', 'WARN']: 1229 dependencies_satisfied = False 1230 break 1231 test_iterations = int(dict.get("iterations", 1)) 1232 test_tag = dict.get("shortname") 1233 1234 if dependencies_satisfied: 1235 # Setting up profilers during test execution. 1236 profilers = dict.get("profilers", "").split() 1237 for profiler in profilers: 1238 job.profilers.add(profiler) 1239 # We need only one execution, profiled, hence we're passing 1240 # the profile_only parameter to job.run_test(). 1241 profile_only = bool(profilers) or None 1242 current_status = job.run_test_detail(dict.get("vm_type"), 1243 params=dict, 1244 tag=test_tag, 1245 iterations=test_iterations, 1246 profile_only=profile_only) 1247 for profiler in profilers: 1248 job.profilers.delete(profiler) 1249 else: 1250 # We will force the test to fail as TestNA during preprocessing 1251 dict['dependency_failed'] = 'yes' 1252 current_status = job.run_test_detail(dict.get("vm_type"), 1253 params=dict, 1254 tag=test_tag, 1255 iterations=test_iterations) 1256 1257 if not current_status: 1258 failed = True 1259 status_dict[dict.get("name")] = current_status 1260 1261 return not failed 1262 1263 1264 def display_attributes(instance): 1265 """ 1266 Inspects a given class instance attributes and displays them, convenient 1267 for debugging. 1268 """ 1269 logging.debug("Attributes set:") 1270 for member in inspect.getmembers(instance): 1271 name, value = member 1272 attribute = getattr(instance, name) 1273 if not (name.startswith("__") or callable(attribute) or not value): 1274 logging.debug(" %s: %s", name, value) 1275 1276 1277 def get_full_pci_id(pci_id): 1278 """ 1279 Get full PCI ID of pci_id. 1280 1281 @param pci_id: PCI ID of a device. 1282 """ 1283 cmd = "lspci -D | awk '/%s/ {print $1}'" % pci_id 1284 status, full_id = commands.getstatusoutput(cmd) 1285 if status != 0: 1286 return None 1287 return full_id 1288 1289 1290 def get_vendor_from_pci_id(pci_id): 1291 """ 1292 Check out the device vendor ID according to pci_id. 1293 1294 @param pci_id: PCI ID of a device. 1295 """ 1296 cmd = "lspci -n | awk '/%s/ {print $3}'" % pci_id 1297 return re.sub(":", " ", commands.getoutput(cmd)) 1298 1299 1300 def get_cpu_flags(): 1301 """ 1302 Returns a list of the CPU flags 1303 """ 1304 flags_re = re.compile(r'^flags\s*:(.*)') 1305 for line in open('/proc/cpuinfo').readlines(): 1306 match = flags_re.match(line) 1307 if match: 1308 return match.groups()[0].split() 1309 return [] 1310 1311 1312 def get_cpu_vendor(cpu_flags=[], verbose=True): 1313 """ 1314 Returns the name of the CPU vendor, either intel, amd or unknown 1315 """ 1316 if not cpu_flags: 1317 cpu_flags = get_cpu_flags() 1318 1319 if 'vmx' in cpu_flags: 1320 vendor = 'intel' 1321 elif 'svm' in cpu_flags: 1322 vendor = 'amd' 1323 else: 1324 vendor = 'unknown' 1325 1326 if verbose: 1327 logging.debug("Detected CPU vendor as '%s'", vendor) 1328 return vendor 1329 1330 1331 def get_archive_tarball_name(source_dir, tarball_name, compression): 1332 ''' 1333 Get the name for a tarball file, based on source, name and compression 1334 ''' 1335 if tarball_name is None: 1336 tarball_name = os.path.basename(source_dir) 1337 1338 if not tarball_name.endswith('.tar'): 1339 tarball_name = '%s.tar' % tarball_name 1340 1341 if compression and not tarball_name.endswith('.%s' % compression): 1342 tarball_name = '%s.%s' % (tarball_name, compression) 1343 1344 return tarball_name 1345 1346 1347 def archive_as_tarball(source_dir, dest_dir, tarball_name=None, 1348 compression='bz2', verbose=True): 1349 ''' 1350 Saves the given source directory to the given destination as a tarball 1351 1352 If the name of the archive is omitted, it will be taken from the 1353 source_dir. If it is an absolute path, dest_dir will be ignored. But, 1354 if both the destination directory and tarball anem is given, and the 1355 latter is not an absolute path, they will be combined. 1356 1357 For archiving directory '/tmp' in '/net/server/backup' as file 1358 'tmp.tar.bz2', simply use: 1359 1360 >>> virt_utils.archive_as_tarball('/tmp', '/net/server/backup') 1361 1362 To save the file it with a different name, say 'host1-tmp.tar.bz2' 1363 and save it under '/net/server/backup', use: 1364 1365 >>> virt_utils.archive_as_tarball('/tmp', '/net/server/backup', 1366 'host1-tmp') 1367 1368 To save with gzip compression instead (resulting in the file 1369 '/net/server/backup/host1-tmp.tar.gz'), use: 1370 1371 >>> virt_utils.archive_as_tarball('/tmp', '/net/server/backup', 1372 'host1-tmp', 'gz') 1373 ''' 1374 tarball_name = get_archive_tarball_name(source_dir, 1375 tarball_name, 1376 compression) 1377 if not os.path.isabs(tarball_name): 1378 tarball_path = os.path.join(dest_dir, tarball_name) 1379 else: 1380 tarball_path = tarball_name 1381 1382 if verbose: 1383 logging.debug('Archiving %s as %s' % (source_dir, 1384 tarball_path)) 1385 1386 os.chdir(os.path.dirname(source_dir)) 1387 tarball = tarfile.TarFile(name=tarball_path, mode='w') 1388 tarball = tarball.open(name=tarball_path, mode='w:%s' % compression) 1389 tarball.add(os.path.basename(source_dir)) 1390 tarball.close() 1391 1392 1393 class Thread(threading.Thread): 1394 """ 1395 Run a function in a background thread. 1396 """ 1397 def __init__(self, target, args=(), kwargs={}): 1398 """ 1399 Initialize the instance. 1400 1401 @param target: Function to run in the thread. 1402 @param args: Arguments to pass to target. 1403 @param kwargs: Keyword arguments to pass to target. 1404 """ 1405 threading.Thread.__init__(self) 1406 self._target = target 1407 self._args = args 1408 self._kwargs = kwargs 1409 1410 1411 def run(self): 1412 """ 1413 Run target (passed to the constructor). No point in calling this 1414 function directly. Call start() to make this function run in a new 1415 thread. 1416 """ 1417 self._e = None 1418 self._retval = None 1419 try: 1420 try: 1421 self._retval = self._target(*self._args, **self._kwargs) 1422 except: 1423 self._e = sys.exc_info() 1424 raise 1425 finally: 1426 # Avoid circular references (start() may be called only once so 1427 # it's OK to delete these) 1428 del self._target, self._args, self._kwargs 1429 1430 1431 def join(self, timeout=None, suppress_exception=False): 1432 """ 1433 Join the thread. If target raised an exception, re-raise it. 1434 Otherwise, return the value returned by target. 1435 1436 @param timeout: Timeout value to pass to threading.Thread.join(). 1437 @param suppress_exception: If True, don't re-raise the exception. 1438 """ 1439 threading.Thread.join(self, timeout) 1440 try: 1441 if self._e: 1442 if not suppress_exception: 1443 # Because the exception was raised in another thread, we 1444 # need to explicitly insert the current context into it 1445 s = error.exception_context(self._e[1]) 1446 s = error.join_contexts(error.get_context(), s) 1447 error.set_exception_context(self._e[1], s) 1448 raise self._e[0], self._e[1], self._e[2] 1449 else: 1450 return self._retval 1451 finally: 1452 # Avoid circular references (join() may be called multiple times 1453 # so we can't delete these) 1454 self._e = None 1455 self._retval = None 1456 1457 1458 def parallel(targets): 1459 """ 1460 Run multiple functions in parallel. 1461 1462 @param targets: A sequence of tuples or functions. If it's a sequence of 1463 tuples, each tuple will be interpreted as (target, args, kwargs) or 1464 (target, args) or (target,) depending on its length. If it's a 1465 sequence of functions, the functions will be called without 1466 arguments. 1467 @return: A list of the values returned by the functions called. 1468 """ 1469 threads = [] 1470 for target in targets: 1471 if isinstance(target, tuple) or isinstance(target, list): 1472 t = Thread(*target) 1473 else: 1474 t = Thread(target) 1475 threads.append(t) 1476 t.start() 1477 return [t.join() for t in threads] 1478 1479 1480 class VirtLoggingConfig(logging_config.LoggingConfig): 1481 """ 1482 Used with the sole purpose of providing convenient logging setup 1483 for the KVM test auxiliary programs. 1484 """ 1485 def configure_logging(self, results_dir=None, verbose=False): 1486 super(VirtLoggingConfig, self).configure_logging(use_console=True, 1487 verbose=verbose) 1488 1489 1490 class PciAssignable(object): 1491 """ 1492 Request PCI assignable devices on host. It will check whether to request 1493 PF (physical Functions) or VF (Virtual Functions). 1494 """ 1495 def __init__(self, type="vf", driver=None, driver_option=None, 1496 names=None, devices_requested=None): 1497 """ 1498 Initialize parameter 'type' which could be: 1499 vf: Virtual Functions 1500 pf: Physical Function (actual hardware) 1501 mixed: Both includes VFs and PFs 1502 1503 If pass through Physical NIC cards, we need to specify which devices 1504 to be assigned, e.g. 'eth1 eth2'. 1505 1506 If pass through Virtual Functions, we need to specify how many vfs 1507 are going to be assigned, e.g. passthrough_count = 8 and max_vfs in 1508 config file. 1509 1510 @param type: PCI device type. 1511 @param driver: Kernel module for the PCI assignable device. 1512 @param driver_option: Module option to specify the maximum number of 1513 VFs (eg 'max_vfs=7') 1514 @param names: Physical NIC cards correspondent network interfaces, 1515 e.g.'eth1 eth2 ...' 1516 @param devices_requested: Number of devices being requested. 1517 """ 1518 self.type = type 1519 self.driver = driver 1520 self.driver_option = driver_option 1521 if names: 1522 self.name_list = names.split() 1523 if devices_requested: 1524 self.devices_requested = int(devices_requested) 1525 else: 1526 self.devices_requested = None 1527 1528 1529 def _get_pf_pci_id(self, name, search_str): 1530 """ 1531 Get the PF PCI ID according to name. 1532 1533 @param name: Name of the PCI device. 1534 @param search_str: Search string to be used on lspci. 1535 """ 1536 cmd = "ethtool -i %s | awk '/bus-info/ {print $2}'" % name 1537 s, pci_id = commands.getstatusoutput(cmd) 1538 if not (s or "Cannot get driver information" in pci_id): 1539 return pci_id[5:] 1540 cmd = "lspci | awk '/%s/ {print $1}'" % search_str 1541 pci_ids = [id for id in commands.getoutput(cmd).splitlines()] 1542 nic_id = int(re.search('[0-9]+', name).group(0)) 1543 if (len(pci_ids) - 1) < nic_id: 1544 return None 1545 return pci_ids[nic_id] 1546 1547 1548 def _release_dev(self, pci_id): 1549 """ 1550 Release a single PCI device. 1551 1552 @param pci_id: PCI ID of a given PCI device. 1553 """ 1554 base_dir = "/sys/bus/pci" 1555 full_id = get_full_pci_id(pci_id) 1556 vendor_id = get_vendor_from_pci_id(pci_id) 1557 drv_path = os.path.join(base_dir, "devices/%s/driver" % full_id) 1558 if 'pci-stub' in os.readlink(drv_path): 1559 cmd = "echo '%s' > %s/new_id" % (vendor_id, drv_path) 1560 if os.system(cmd): 1561 return False 1562 1563 stub_path = os.path.join(base_dir, "drivers/pci-stub") 1564 cmd = "echo '%s' > %s/unbind" % (full_id, stub_path) 1565 if os.system(cmd): 1566 return False 1567 1568 driver = self.dev_drivers[pci_id] 1569 cmd = "echo '%s' > %s/bind" % (full_id, driver) 1570 if os.system(cmd): 1571 return False 1572 1573 return True 1574 1575 1576 def get_vf_devs(self): 1577 """ 1578 Catch all VFs PCI IDs. 1579 1580 @return: List with all PCI IDs for the Virtual Functions avaliable 1581 """ 1582 if not self.sr_iov_setup(): 1583 return [] 1584 1585 cmd = "lspci | awk '/Virtual Function/ {print $1}'" 1586 return commands.getoutput(cmd).split() 1587 1588 1589 def get_pf_devs(self): 1590 """ 1591 Catch all PFs PCI IDs. 1592 1593 @return: List with all PCI IDs for the physical hardware requested 1594 """ 1595 pf_ids = [] 1596 for name in self.name_list: 1597 pf_id = self._get_pf_pci_id(name, "Ethernet") 1598 if not pf_id: 1599 continue 1600 pf_ids.append(pf_id) 1601 return pf_ids 1602 1603 1604 def get_devs(self, count): 1605 """ 1606 Check out all devices' PCI IDs according to their name. 1607 1608 @param count: count number of PCI devices needed for pass through 1609 @return: a list of all devices' PCI IDs 1610 """ 1611 if self.type == "vf": 1612 vf_ids = self.get_vf_devs() 1613 elif self.type == "pf": 1614 vf_ids = self.get_pf_devs() 1615 elif self.type == "mixed": 1616 vf_ids = self.get_vf_devs() 1617 vf_ids.extend(self.get_pf_devs()) 1618 return vf_ids[0:count] 1619 1620 1621 def get_vfs_count(self): 1622 """ 1623 Get VFs count number according to lspci. 1624 """ 1625 # FIXME: Need to think out a method of identify which 1626 # 'virtual function' belongs to which physical card considering 1627 # that if the host has more than one 82576 card. PCI_ID? 1628 cmd = "lspci | grep 'Virtual Function' | wc -l" 1629 return int(commands.getoutput(cmd)) 1630 1631 1632 def check_vfs_count(self): 1633 """ 1634 Check VFs count number according to the parameter driver_options. 1635 """ 1636 # Network card 82576 has two network interfaces and each can be 1637 # virtualized up to 7 virtual functions, therefore we multiply 1638 # two for the value of driver_option 'max_vfs'. 1639 expected_count = int((re.findall("(\d)", self.driver_option)[0])) * 2 1640 return (self.get_vfs_count == expected_count) 1641 1642 1643 def is_binded_to_stub(self, full_id): 1644 """ 1645 Verify whether the device with full_id is already binded to pci-stub. 1646 1647 @param full_id: Full ID for the given PCI device 1648 """ 1649 base_dir = "/sys/bus/pci" 1650 stub_path = os.path.join(base_dir, "drivers/pci-stub") 1651 if os.path.exists(os.path.join(stub_path, full_id)): 1652 return True 1653 return False 1654 1655 1656 def sr_iov_setup(self): 1657 """ 1658 Ensure the PCI device is working in sr_iov mode. 1659 1660 Check if the PCI hardware device drive is loaded with the appropriate, 1661 parameters (number of VFs), and if it's not, perform setup. 1662 1663 @return: True, if the setup was completed successfuly, False otherwise. 1664 """ 1665 re_probe = False 1666 s, o = commands.getstatusoutput('lsmod | grep %s' % self.driver) 1667 if s: 1668 re_probe = True 1669 elif not self.check_vfs_count(): 1670 os.system("modprobe -r %s" % self.driver) 1671 re_probe = True 1672 else: 1673 return True 1674 1675 # Re-probe driver with proper number of VFs 1676 if re_probe: 1677 cmd = "modprobe %s %s" % (self.driver, self.driver_option) 1678 logging.info("Loading the driver '%s' with option '%s'", 1679 self.driver, self.driver_option) 1680 s, o = commands.getstatusoutput(cmd) 1681 if s: 1682 return False 1683 return True 1684 1685 1686 def request_devs(self): 1687 """ 1688 Implement setup process: unbind the PCI device and then bind it 1689 to the pci-stub driver. 1690 1691 @return: a list of successfully requested devices' PCI IDs. 1692 """ 1693 base_dir = "/sys/bus/pci" 1694 stub_path = os.path.join(base_dir, "drivers/pci-stub") 1695 1696 self.pci_ids = self.get_devs(self.devices_requested) 1697 logging.debug("The following pci_ids were found: %s", self.pci_ids) 1698 requested_pci_ids = [] 1699 self.dev_drivers = {} 1700 1701 # Setup all devices specified for assignment to guest 1702 for pci_id in self.pci_ids: 1703 full_id = get_full_pci_id(pci_id) 1704 if not full_id: 1705 continue 1706 drv_path = os.path.join(base_dir, "devices/%s/driver" % full_id) 1707 dev_prev_driver = os.path.realpath(os.path.join(drv_path, 1708 os.readlink(drv_path))) 1709 self.dev_drivers[pci_id] = dev_prev_driver 1710 1711 # Judge whether the device driver has been binded to stub 1712 if not self.is_binded_to_stub(full_id): 1713 logging.debug("Binding device %s to stub", full_id) 1714 vendor_id = get_vendor_from_pci_id(pci_id) 1715 stub_new_id = os.path.join(stub_path, 'new_id') 1716 unbind_dev = os.path.join(drv_path, 'unbind') 1717 stub_bind = os.path.join(stub_path, 'bind') 1718 1719 info_write_to_files = [(vendor_id, stub_new_id), 1720 (full_id, unbind_dev), 1721 (full_id, stub_bind)] 1722 1723 for content, file in info_write_to_files: 1724 try: 1725 utils.open_write_close(file, content) 1726 except IOError: 1727 logging.debug("Failed to write %s to file %s", content, 1728 file) 1729 continue 1730 1731 if not self.is_binded_to_stub(full_id): 1732 logging.error("Binding device %s to stub failed", pci_id) 1733 continue 1734 else: 1735 logging.debug("Device %s already binded to stub", pci_id) 1736 requested_pci_ids.append(pci_id) 1737 self.pci_ids = requested_pci_ids 1738 return self.pci_ids 1739 1740 1741 def release_devs(self): 1742 """ 1743 Release all PCI devices currently assigned to VMs back to the 1744 virtualization host. 1745 """ 1746 try: 1747 for pci_id in self.dev_drivers: 1748 if not self._release_dev(pci_id): 1749 logging.error("Failed to release device %s to host", pci_id) 1750 else: 1751 logging.info("Released device %s successfully", pci_id) 1752 except: 1753 return 1754 1755 1756 class KojiClient(object): 1757 """ 1758 Stablishes a connection with the build system, either koji or brew. 1759 1760 This class provides convenience methods to retrieve information on packages 1761 and the packages themselves hosted on the build system. Packages should be 1762 specified in the KojiPgkSpec syntax. 1763 """ 1764 1765 CMD_LOOKUP_ORDER = ['/usr/bin/brew', '/usr/bin/koji' ] 1766 1767 CONFIG_MAP = {'/usr/bin/brew': '/etc/brewkoji.conf', 1768 '/usr/bin/koji': '/etc/koji.conf'} 1769 1770 1771 def __init__(self, cmd=None): 1772 """ 1773 Verifies whether the system has koji or brew installed, then loads 1774 the configuration file that will be used to download the files. 1775 1776 @type cmd: string 1777 @param cmd: Optional command name, either 'brew' or 'koji'. If not 1778 set, get_default_command() is used and to look for 1779 one of them. 1780 @raise: ValueError 1781 """ 1782 if not KOJI_INSTALLED: 1783 raise ValueError('No koji/brew installed on the machine') 1784 1785 # Instance variables used by many methods 1786 self.command = None 1787 self.config = None 1788 self.config_options = {} 1789 self.session = None 1790 1791 # Set koji command or get default 1792 if cmd is None: 1793 self.command = self.get_default_command() 1794 else: 1795 self.command = cmd 1796 1797 # Check koji command 1798 if not self.is_command_valid(): 1799 raise ValueError('Koji command "%s" is not valid' % self.command) 1800 1801 # Assuming command is valid, set configuration file and read it 1802 self.config = self.CONFIG_MAP[self.command] 1803 self.read_config() 1804 1805 # Setup koji session 1806 server_url = self.config_options['server'] 1807 session_options = self.get_session_options() 1808 self.session = koji.ClientSession(server_url, 1809 session_options) 1810 1811 1812 def read_config(self, check_is_valid=True): 1813 ''' 1814 Reads options from the Koji configuration file 1815 1816 By default it checks if the koji configuration is valid 1817 1818 @type check_valid: boolean 1819 @param check_valid: whether to include a check on the configuration 1820 @raises: ValueError 1821 @returns: None 1822 ''' 1823 if check_is_valid: 1824 if not self.is_config_valid(): 1825 raise ValueError('Koji config "%s" is not valid' % self.config) 1826 1827 config = ConfigParser.ConfigParser() 1828 config.read(self.config) 1829 1830 basename = os.path.basename(self.command) 1831 for name, value in config.items(basename): 1832 self.config_options[name] = value 1833 1834 1835 def get_session_options(self): 1836 ''' 1837 Filter only options necessary for setting up a cobbler client session 1838 1839 @returns: only the options used for session setup 1840 ''' 1841 session_options = {} 1842 for name, value in self.config_options.items(): 1843 if name in ('user', 'password', 'debug_xmlrpc', 'debug'): 1844 session_options[name] = value 1845 return session_options 1846 1847 1848 def is_command_valid(self): 1849 ''' 1850 Checks if the currently set koji command is valid 1851 1852 @returns: True or False 1853 ''' 1854 koji_command_ok = True 1855 1856 if not os.path.isfile(self.command): 1857 logging.error('Koji command "%s" is not a regular file', 1858 self.command) 1859 koji_command_ok = False 1860 1861 if not os.access(self.command, os.X_OK): 1862 logging.warning('Koji command "%s" is not executable: this is ' 1863 'not fatal but indicates an unexpected situation', 1864 self.command) 1865 1866 if not self.command in self.CONFIG_MAP.keys(): 1867 logging.error('Koji command "%s" does not have a configuration ' 1868 'file associated to it', self.command) 1869 koji_command_ok = False 1870 1871 return koji_command_ok 1872 1873 1874 def is_config_valid(self): 1875 ''' 1876 Checks if the currently set koji configuration is valid 1877 1878 @returns: True or False 1879 ''' 1880 koji_config_ok = True 1881 1882 if not os.path.isfile(self.config): 1883 logging.error('Koji config "%s" is not a regular file', self.config) 1884 koji_config_ok = False 1885 1886 if not os.access(self.config, os.R_OK): 1887 logging.error('Koji config "%s" is not readable', self.config) 1888 koji_config_ok = False 1889 1890 config = ConfigParser.ConfigParser() 1891 config.read(self.config) 1892 basename = os.path.basename(self.command) 1893 if not config.has_section(basename): 1894 logging.error('Koji configuration file "%s" does not have a ' 1895 'section "%s", named after the base name of the ' 1896 'currently set koji command "%s"', self.config, 1897 basename, self.command) 1898 koji_config_ok = False 1899 1900 return koji_config_ok 1901 1902 1903 def get_default_command(self): 1904 ''' 1905 Looks up for koji or brew "binaries" on the system 1906 1907 Systems with plain koji usually don't have a brew cmd, while systems 1908 with koji, have *both* koji and brew utilities. So we look for brew 1909 first, and if found, we consider that the system is configured for 1910 brew. If not, we consider this is a system with plain koji. 1911 1912 @returns: either koji or brew command line executable path, or None 1913 ''' 1914 koji_command = None 1915 for command in self.CMD_LOOKUP_ORDER: 1916 if os.path.isfile(command): 1917 koji_command = command 1918 break 1919 else: 1920 koji_command_basename = os.path.basename(koji_command) 1921 try: 1922 koji_command = os_dep.command(koji_command_basename) 1923 break 1924 except ValueError: 1925 pass 1926 return koji_command 1927 1928 1929 def get_pkg_info(self, pkg): 1930 ''' 1931 Returns information from Koji on the package 1932 1933 @type pkg: KojiPkgSpec 1934 @param pkg: information about the package, as a KojiPkgSpec instance 1935 1936 @returns: information from Koji about the specified package 1937 ''' 1938 info = {} 1939 if pkg.build is not None: 1940 info = self.session.getBuild(int(pkg.build)) 1941 elif pkg.tag is not None and pkg.package is not None: 1942 builds = self.session.listTagged(pkg.tag, 1943 latest=True, 1944 inherit=True, 1945 package=pkg.package) 1946 if builds: 1947 info = builds[0] 1948 return info 1949 1950 1951 def is_pkg_valid(self, pkg): 1952 ''' 1953 Checks if this package is altogether valid on Koji 1954 1955 This verifies if the build or tag specified in the package 1956 specification actually exist on the Koji server 1957 1958 @returns: True or False 1959 ''' 1960 valid = True 1961 if pkg.build: 1962 if not self.is_pkg_spec_build_valid(pkg): 1963 valid = False 1964 elif pkg.tag: 1965 if not self.is_pkg_spec_tag_valid(pkg): 1966 valid = False 1967 else: 1968 valid = False 1969 return valid 1970 1971 1972 def is_pkg_spec_build_valid(self, pkg): 1973 ''' 1974 Checks if build is valid on Koji 1975 1976 @param pkg: a Pkg instance 1977 ''' 1978 if pkg.build is not None: 1979 info = self.session.getBuild(int(pkg.build)) 1980 if info: 1981 return True 1982 return False 1983 1984 1985 def is_pkg_spec_tag_valid(self, pkg): 1986 ''' 1987 Checks if tag is valid on Koji 1988 1989 @type pkg: KojiPkgSpec 1990 @param pkg: a package specification 1991 ''' 1992 if pkg.tag is not None: 1993 tag = self.session.getTag(pkg.tag) 1994 if tag: 1995 return True 1996 return False 1997 1998 1999 def get_pkg_rpm_info(self, pkg, arch=None): 2000 ''' 2001 Returns a list of infomation on the RPM packages found on koji 2002 2003 @type pkg: KojiPkgSpec 2004 @param pkg: a package specification 2005 @type arch: string 2006 @param arch: packages built for this architecture, but also including 2007 architecture independent (noarch) packages 2008 ''' 2009 if arch is None: 2010 arch = utils.get_arch() 2011 rpms = [] 2012 info = self.get_pkg_info(pkg) 2013 if info: 2014 rpms = self.session.listRPMs(buildID=info['id'], 2015 arches=[arch, 'noarch']) 2016 if pkg.subpackages: 2017 rpms = [d for d in rpms if d['name'] in pkg.subpackages] 2018 return rpms 2019 2020 2021 def get_pkg_rpm_names(self, pkg, arch=None): 2022 ''' 2023 Gets the names for the RPM packages specified in pkg 2024 2025 @type pkg: KojiPkgSpec 2026 @param pkg: a package specification 2027 @type arch: string 2028 @param arch: packages built for this architecture, but also including 2029 architecture independent (noarch) packages 2030 ''' 2031 if arch is None: 2032 arch = utils.get_arch() 2033 rpms = self.get_pkg_rpm_info(pkg, arch) 2034 return [rpm['name'] for rpm in rpms] 2035 2036 2037 def get_pkg_rpm_file_names(self, pkg, arch=None): 2038 ''' 2039 Gets the file names for the RPM packages specified in pkg 2040 2041 @type pkg: KojiPkgSpec 2042 @param pkg: a package specification 2043 @type arch: string 2044 @param arch: packages built for this architecture, but also including 2045 architecture independent (noarch) packages 2046 ''' 2047 if arch is None: 2048 arch = utils.get_arch() 2049 rpm_names = [] 2050 rpms = self.get_pkg_rpm_info(pkg, arch) 2051 for rpm in rpms: 2052 arch_rpm_name = koji.pathinfo.rpm(rpm) 2053 rpm_name = os.path.basename(arch_rpm_name) 2054 rpm_names.append(rpm_name) 2055 return rpm_names 2056 2057 2058 def get_pkg_urls(self, pkg, arch=None): 2059 ''' 2060 Gets the urls for the packages specified in pkg 2061 2062 @type pkg: KojiPkgSpec 2063 @param pkg: a package specification 2064 @type arch: string 2065 @param arch: packages built for this architecture, but also including 2066 architecture independent (noarch) packages 2067 ''' 2068 info = self.get_pkg_info(pkg) 2069 rpms = self.get_pkg_rpm_info(pkg, arch) 2070 rpm_urls = [] 2071 for rpm in rpms: 2072 rpm_name = koji.pathinfo.rpm(rpm) 2073 url = ("%s/%s/%s/%s/%s" % (self.config_options['pkgurl'], 2074 info['package_name'], 2075 info['version'], info['release'], 2076 rpm_name)) 2077 rpm_urls.append(url) 2078 return rpm_urls 2079 2080 2081 def get_pkgs(self, pkg, dst_dir, arch=None): 2082 ''' 2083 Download the packages 2084 2085 @type pkg: KojiPkgSpec 2086 @param pkg: a package specification 2087 @type dst_dir: string 2088 @param dst_dir: the destination directory, where the downloaded 2089 packages will be saved on 2090 @type arch: string 2091 @param arch: packages built for this architecture, but also including 2092 architecture independent (noarch) packages 2093 ''' 2094 rpm_urls = self.get_pkg_urls(pkg, arch) 2095 for url in rpm_urls: 2096 utils.get_file(url, 2097 os.path.join(dst_dir, os.path.basename(url))) 2098 2099 2100 DEFAULT_KOJI_TAG = None 2101 def set_default_koji_tag(tag): 2102 ''' 2103 Sets the default tag that will be used 2104 ''' 2105 global DEFAULT_KOJI_TAG 2106 DEFAULT_KOJI_TAG = tag 2107 2108 2109 def get_default_koji_tag(): 2110 return DEFAULT_KOJI_TAG 2111 2112 2113 class KojiPkgSpec(object): 2114 ''' 2115 A package specification syntax parser for Koji 2116 2117 This holds information on either tag or build, and packages to be fetched 2118 from koji and possibly installed (features external do this class). 2119 2120 New objects can be created either by providing information in the textual 2121 format or by using the actual parameters for tag, build, package and sub- 2122 packages. The textual format is useful for command line interfaces and 2123 configuration files, while using parameters is better for using this in 2124 a programatic fashion. 2125 2126 The following sets of examples are interchangeable. Specifying all packages 2127 part of build number 1000: 2128 2129 >>> from kvm_utils import KojiPkgSpec 2130 >>> pkg = KojiPkgSpec('1000') 2131 2132 >>> pkg = KojiPkgSpec(build=1000) 2133 2134 Specifying only a subset of packages of build number 1000: 2135 2136 >>> pkg = KojiPkgSpec('1000:kernel,kernel-devel') 2137 2138 >>> pkg = KojiPkgSpec(build=1000, 2139 subpackages=['kernel', 'kernel-devel']) 2140 2141 Specifying the latest build for the 'kernel' package tagged with 'dist-f14': 2142 2143 >>> pkg = KojiPkgSpec('dist-f14:kernel') 2144 2145 >>> pkg = KojiPkgSpec(tag='dist-f14', package='kernel') 2146 2147 Specifying the 'kernel' package using the default tag: 2148 2149 >>> kvm_utils.set_default_koji_tag('dist-f14') 2150 >>> pkg = KojiPkgSpec('kernel') 2151 2152 >>> pkg = KojiPkgSpec(package='kernel') 2153 2154 Specifying the 'kernel' package using the default tag: 2155 2156 >>> kvm_utils.set_default_koji_tag('dist-f14') 2157 >>> pkg = KojiPkgSpec('kernel') 2158 2159 >>> pkg = KojiPkgSpec(package='kernel') 2160 2161 If you do not specify a default tag, and give a package name without an 2162 explicit tag, your package specification is considered invalid: 2163 2164 >>> print kvm_utils.get_default_koji_tag() 2165 None 2166 >>> print kvm_utils.KojiPkgSpec('kernel').is_valid() 2167 False 2168 2169 >>> print kvm_utils.KojiPkgSpec(package='kernel').is_valid() 2170 False 2171 ''' 2172 2173 SEP = ':' 2174 2175 def __init__(self, text='', tag=None, build=None, 2176 package=None, subpackages=[]): 2177 ''' 2178 Instantiates a new KojiPkgSpec object 2179 2180 @type text: string 2181 @param text: a textual representation of a package on Koji that 2182 will be parsed 2183 @type tag: string 2184 @param tag: a koji tag, example: Fedora-14-RELEASE 2185 (see U{http://fedoraproject.org/wiki/Koji#Tags_and_Targets}) 2186 @type build: number 2187 @param build: a koji build, example: 1001 2188 (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture}) 2189 @type package: string 2190 @param package: a koji package, example: python 2191 (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture}) 2192 @type subpackages: list of strings 2193 @param subpackages: a list of package names, usually a subset of 2194 the RPM packages generated by a given build 2195 ''' 2196 2197 # Set to None to indicate 'not set' (and be able to use 'is') 2198 self.tag = None 2199 self.build = None 2200 self.package = None 2201 self.subpackages = [] 2202 2203 self.default_tag = None 2204 2205 # Textual representation takes precedence (most common use case) 2206 if text: 2207 self.parse(text) 2208 else: 2209 self.tag = tag 2210 self.build = build 2211 self.package = package 2212 self.subpackages = subpackages 2213 2214 # Set the default tag, if set, as a fallback 2215 if not self.build and not self.tag: 2216 default_tag = get_default_koji_tag() 2217 if default_tag is not None: 2218 self.tag = default_tag 2219 2220 2221 def parse(self, text): 2222 ''' 2223 Parses a textual representation of a package specification 2224 2225 @type text: string 2226 @param text: textual representation of a package in koji 2227 ''' 2228 parts = text.count(self.SEP) + 1 2229 if parts == 1: 2230 if text.isdigit(): 2231 self.build = text 2232 else: 2233 self.package = text 2234 elif parts == 2: 2235 part1, part2 = text.split(self.SEP) 2236 if part1.isdigit(): 2237 self.build = part1 2238 self.subpackages = part2.split(',') 2239 else: 2240 self.tag = part1 2241 self.package = part2 2242 elif parts >= 3: 2243 # Instead of erroring on more arguments, we simply ignore them 2244 # This makes the parser suitable for future syntax additions, such 2245 # as specifying the package architecture 2246 part1, part2, part3 = text.split(self.SEP)[0:3] 2247 self.tag = part1 2248 self.package = part2 2249 self.subpackages = part3.split(',') 2250 2251 2252 def _is_invalid_neither_tag_or_build(self): 2253 ''' 2254 Checks if this package is invalid due to not having either a valid 2255 tag or build set, that is, both are empty. 2256 2257 @returns: True if this is invalid and False if it's valid 2258 ''' 2259 return (self.tag is None and self.build is None) 2260 2261 2262 def _is_invalid_package_but_no_tag(self): 2263 ''' 2264 Checks if this package is invalid due to having a package name set 2265 but tag or build set, that is, both are empty. 2266 2267 @returns: True if this is invalid and False if it's valid 2268 ''' 2269 return (self.package and not self.tag) 2270 2271 2272 def _is_invalid_subpackages_but_no_main_package(self): 2273 ''' 2274 Checks if this package is invalid due to having a tag set (this is Ok) 2275 but specifying subpackage names without specifying the main package 2276 name. 2277 2278 Specifying subpackages without a main package name is only valid when 2279 a build is used instead of a tag. 2280 2281 @returns: True if this is invalid and False if it's valid 2282 ''' 2283 return (self.tag and self.subpackages and not self.package) 2284 2285 2286 def is_valid(self): 2287 ''' 2288 Checks if this package specification is valid. 2289 2290 Being valid means that it has enough and not conflicting information. 2291 It does not validate that the packages specified actually existe on 2292 the Koji server. 2293 2294 @returns: True or False 2295 ''' 2296 if self._is_invalid_neither_tag_or_build(): 2297 return False 2298 elif self._is_invalid_package_but_no_tag(): 2299 return False 2300 elif self._is_invalid_subpackages_but_no_main_package(): 2301 return False 2302 2303 return True 2304 2305 2306 def describe_invalid(self): 2307 ''' 2308 Describes why this is not valid, in a human friendly way 2309 ''' 2310 if self._is_invalid_neither_tag_or_build(): 2311 return 'neither a tag or build are set, and of them should be set' 2312 elif self._is_invalid_package_but_no_tag(): 2313 return 'package name specified but no tag is set' 2314 elif self._is_invalid_subpackages_but_no_main_package(): 2315 return 'subpackages specified but no main package is set' 2316 2317 return 'unkwown reason, seems to be valid' 2318 2319 2320 def describe(self): 2321 ''' 2322 Describe this package specification, in a human friendly way 2323 2324 @returns: package specification description 2325 ''' 2326 if self.is_valid(): 2327 description = '' 2328 if not self.subpackages: 2329 description += 'all subpackages from %s ' % self.package 2330 else: 2331 description += ('only subpackage(s) %s from package %s ' % 2332 (', '.join(self.subpackages), self.package)) 2333 2334 if self.build: 2335 description += 'from build %s' % self.build 2336 elif self.tag: 2337 description += 'tagged with %s' % self.tag 2338 else: 2339 raise ValueError, 'neither build or tag is set' 2340 2341 return description 2342 else: 2343 return ('Invalid package specification: %s' % 2344 self.describe_invalid()) 2345 2346 2347 def __repr__(self): 2348 return ("<KojiPkgSpec tag=%s build=%s pkg=%s subpkgs=%s>" % 2349 (self.tag, self.build, self.package, 2350 ", ".join(self.subpackages))) 2351 2352 2353 def umount(src, mount_point, type): 2354 """ 2355 Umount the src mounted in mount_point. 2356 2357 @src: mount source 2358 @mount_point: mount point 2359 @type: file system type 2360 """ 2361 2362 mount_string = "%s %s %s" % (src, mount_point, type) 2363 if mount_string in file("/etc/mtab").read(): 2364 umount_cmd = "umount %s" % mount_point 2365 try: 2366 utils.system(umount_cmd) 2367 return True 2368 except error.CmdError: 2369 return False 2370 else: 2371 logging.debug("%s is not mounted under %s", src, mount_point) 2372 return True 2373 2374 2375 def mount(src, mount_point, type, perm="rw"): 2376 """ 2377 Mount the src into mount_point of the host. 2378 2379 @src: mount source 2380 @mount_point: mount point 2381 @type: file system type 2382 @perm: mount premission 2383 """ 2384 umount(src, mount_point, type) 2385 mount_string = "%s %s %s %s" % (src, mount_point, type, perm) 2386 2387 if mount_string in file("/etc/mtab").read(): 2388 logging.debug("%s is already mounted in %s with %s", 2389 src, mount_point, perm) 2390 return True 2391 2392 mount_cmd = "mount -t %s %s %s -o %s" % (type, src, mount_point, perm) 2393 try: 2394 utils.system(mount_cmd) 2395 except error.CmdError: 2396 return False 2397 2398 logging.debug("Verify the mount through /etc/mtab") 2399 if mount_string in file("/etc/mtab").read(): 2400 logging.debug("%s is successfully mounted", src) 2401 return True 2402 else: 2403 logging.error("Can't find mounted NFS share - /etc/mtab contents \n%s", 2404 file("/etc/mtab").read()) 2405 return False 2406 2407 2408 class GitRepoHelper(object): 2409 ''' 2410 Helps to deal with git repos, mostly fetching content from a repo 2411 ''' 2412 def __init__(self, uri, branch, destination_dir, commit=None, lbranch=None): 2413 ''' 2414 Instantiates a new GitRepoHelper 2415 2416 @type uri: string 2417 @param uri: git repository url 2418 @type branch: string 2419 @param branch: git remote branch 2420 @type destination_dir: string 2421 @param destination_dir: path of a dir where to save downloaded code 2422 @type commit: string 2423 @param commit: specific commit to download 2424 @type lbranch: string 2425 @param lbranch: git local branch name, if different from remote 2426 ''' 2427 self.uri = uri 2428 self.branch = branch 2429 self.destination_dir = destination_dir 2430 self.commit = commit 2431 if lbranch is None: 2432 self.lbranch = branch 2433 2434 2435 def init(self): 2436 ''' 2437 Initializes a directory for receiving a verbatim copy of git repo 2438 2439 This creates a directory if necessary, and either resets or inits 2440 the repo 2441 ''' 2442 if not os.path.exists(self.destination_dir): 2443 logging.debug('Creating directory %s for git repo %s', 2444 self.destination_dir, self.uri) 2445 os.makedirs(self.destination_dir) 2446 2447 os.chdir(self.destination_dir) 2448 2449 if os.path.exists('.git'): 2450 logging.debug('Resetting previously existing git repo at %s for ' 2451 'receiving git repo %s', 2452 self.destination_dir, self.uri) 2453 utils.system('git reset --hard') 2454 else: 2455 logging.debug('Initializing new git repo at %s for receiving ' 2456 'git repo %s', 2457 self.destination_dir, self.uri) 2458 utils.system('git init') 2459 2460 2461 def fetch(self): 2462 ''' 2463 Performs a git fetch from the remote repo 2464 ''' 2465 logging.info("Fetching git [REP '%s' BRANCH '%s'] -> %s", 2466 self.uri, self.branch, self.destination_dir) 2467 os.chdir(self.destination_dir) 2468 utils.system("git fetch -q -f -u -t %s %s:%s" % (self.uri, 2469 self.branch, 2470 self.lbranch)) 2471 2472 2473 def checkout(self): 2474 ''' 2475 Performs a git checkout for a given branch and start point (commit) 2476 ''' 2477 os.chdir(self.destination_dir) 2478 2479 logging.debug('Checking out local branch %s', self.lbranch) 2480 utils.system("git checkout %s" % self.lbranch) 2481 2482 if self.commit is not None: 2483 logging.debug('Checking out commit %s', self.commit) 2484 utils.system("git checkout %s" % self.commit) 2485 2486 h = utils.system_output('git log --pretty=format:"%H" -1').strip() 2487 try: 2488 desc = "tag %s" % utils.system_output("git describe") 2489 except error.CmdError: 2490 desc = "no tag found" 2491 2492 logging.info("Commit hash for %s is %s (%s)", self.name, h, desc) 2493 2494 2495 def execute(self): 2496 ''' 2497 Performs all steps necessary to initialize and download a git repo 2498 2499 This includes the init, fetch and checkout steps in one single 2500 utility method. 2501 ''' 2502 self.init() 2503 self.fetch() 2504 self.checkout() 2505 2506 2507 class GitRepoParamHelper(GitRepoHelper): 2508 ''' 2509 Helps to deal with git repos specified in cartersian config files 2510 2511 This class attempts to make it simple to manage a git repo, by using a 2512 naming standard that follows this basic syntax: 2513 2514 <prefix>_name_<suffix> 2515 2516 <prefix> is always 'git_repo' and <suffix> sets options for this git repo. 2517 Example for repo named foo: 2518 2519 git_repo_foo_uri = git://git.foo.org/foo.git 2520 git_repo_foo_branch = master 2521 git_repo_foo_lbranch = master 2522 git_repo_foo_commit = bb5fb8e678aabe286e74c4f2993dc2a9e550b627 2523 ''' 2524 def __init__(self, params, name, destination_dir): 2525 ''' 2526 Instantiates a new GitRepoParamHelper 2527 ''' 2528 self.params = params 2529 self.name = name 2530 self.destination_dir = destination_dir 2531 self._parse_params() 2532 2533 2534 def _parse_params(self): 2535 ''' 2536 Parses the params items for entries related to this repo 2537 2538 This method currently does everything that the parent class __init__() 2539 method does, that is, sets all instance variables needed by other 2540 methods. That means it's not strictly necessary to call parent's 2541 __init__(). 2542 ''' 2543 config_prefix = 'git_repo_%s' % self.name 2544 logging.debug('Parsing parameters for git repo %s, configuration ' 2545 'prefix is %s' % (self.name, config_prefix)) 2546 2547 self.uri = self.params.get('%s_uri' % config_prefix) 2548 logging.debug('Git repo %s uri: %s' % (self.name, self.uri)) 2549 2550 self.branch = self.params.get('%s_branch' % config_prefix, 'master') 2551 logging.debug('Git repo %s branch: %s' % (self.name, self.branch)) 2552 2553 self.lbranch = self.params.get('%s_lbranch' % config_prefix) 2554 if self.lbranch is None: 2555 self.lbranch = self.branch 2556 logging.debug('Git repo %s lbranch: %s' % (self.name, self.lbranch)) 2557 2558 self.commit = self.params.get('%s_commit' % config_prefix) 2559 if self.commit is None: 2560 logging.debug('Git repo %s commit is not set' % self.name) 2561 else: 2562 logging.debug('Git repo %s commit: %s' % (self.name, self.commit)) 2563 2564 2565 class LocalSourceDirHelper(object): 2566 ''' 2567 Helper class to deal with source code sitting somewhere in the filesystem 2568 ''' 2569 def __init__(self, source_dir, destination_dir): 2570 ''' 2571 @param source_dir: 2572 @param destination_dir: 2573 @return: new LocalSourceDirHelper instance 2574 ''' 2575 self.source = source_dir 2576 self.destination = destination_dir 2577 2578 2579 def execute(self): 2580 ''' 2581 Copies the source directory to the destination directory 2582 ''' 2583 if os.path.isdir(self.destination): 2584 shutil.rmtree(self.destination) 2585 2586 if os.path.isdir(self.source): 2587 shutil.copytree(self.source, self.destination) 2588 2589 2590 class LocalSourceDirParamHelper(LocalSourceDirHelper): 2591 ''' 2592 Helps to deal with source dirs specified in cartersian config files 2593 2594 This class attempts to make it simple to manage a source dir, by using a 2595 naming standard that follows this basic syntax: 2596 2597 <prefix>_name_<suffix> 2598 2599 <prefix> is always 'local_src' and <suffix> sets options for this source 2600 dir. Example for source dir named foo: 2601 2602 local_src_foo_path = /home/user/foo 2603 ''' 2604 def __init__(self, params, name, destination_dir): 2605 ''' 2606 Instantiate a new LocalSourceDirParamHelper 2607 ''' 2608 self.params = params 2609 self.name = name 2610 self.destination_dir = destination_dir 2611 self._parse_params() 2612 2613 2614 def _parse_params(self): 2615 ''' 2616 Parses the params items for entries related to source dir 2617 ''' 2618 config_prefix = 'local_src_%s' % self.name 2619 logging.debug('Parsing parameters for local source %s, configuration ' 2620 'prefix is %s' % (self.name, config_prefix)) 2621 2622 self.path = self.params.get('%s_path' % config_prefix) 2623 logging.debug('Local source directory %s path: %s' % (self.name, 2624 self.path)) 2625 self.source = self.path 2626 self.destination = self.destination_dir 2627 2628 2629 class LocalTarHelper(object): 2630 ''' 2631 Helper class to deal with source code in a local tarball 2632 ''' 2633 def __init__(self, source, destination_dir): 2634 self.source = source 2635 self.destination = destination_dir 2636 2637 2638 def extract(self): 2639 ''' 2640 Extracts the tarball into the destination directory 2641 ''' 2642 if os.path.isdir(self.destination): 2643 shutil.rmtree(self.destination) 2644 2645 if os.path.isfile(self.source) and tarfile.is_tarfile(self.source): 2646 2647 name = os.path.basename(self.destination) 2648 temp_dir = os.path.join(os.path.dirname(self.destination), 2649 '%s.tmp' % name) 2650 logging.debug('Temporary directory for extracting tarball is %s' % 2651 temp_dir) 2652 2653 if not os.path.isdir(temp_dir): 2654 os.makedirs(temp_dir) 2655 2656 tarball = tarfile.open(self.source) 2657 tarball.extractall(temp_dir) 2658 2659 # 2660 # If there's a directory at the toplevel of the tarfile, assume 2661 # it's the root for the contents, usually source code 2662 # 2663 tarball_info = tarball.members[0] 2664 if tarball_info.isdir(): 2665 content_path = os.path.join(temp_dir, 2666 tarball_info.name) 2667 else: 2668 content_path = temp_dir 2669 2670 # 2671 # Now move the content directory to the final destination 2672 # 2673 shutil.move(content_path, self.destination) 2674 2675 else: 2676 raise OSError("%s is not a file or tar file" % self.source) 2677 2678 2679 def execute(self): 2680 ''' 2681 Executes all action this helper is suposed to perform 2682 2683 This is the main entry point method for this class, and all other 2684 helper classes. 2685 ''' 2686 self.extract() 2687 2688 2689 class LocalTarParamHelper(LocalTarHelper): 2690 ''' 2691 Helps to deal with source tarballs specified in cartersian config files 2692 2693 This class attempts to make it simple to manage a tarball with source code, 2694 by using a naming standard that follows this basic syntax: 2695 2696 <prefix>_name_<suffix> 2697 2698 <prefix> is always 'local_tar' and <suffix> sets options for this source 2699 tarball. Example for source tarball named foo: 2700 2701 local_tar_foo_path = /tmp/foo-1.0.tar.gz 2702 ''' 2703 def __init__(self, params, name, destination_dir): 2704 ''' 2705 Instantiates a new LocalTarParamHelper 2706 ''' 2707 self.params = params 2708 self.name = name 2709 self.destination_dir = destination_dir 2710 self._parse_params() 2711 2712 2713 def _parse_params(self): 2714 ''' 2715 Parses the params items for entries related to this local tar helper 2716 ''' 2717 config_prefix = 'local_tar_%s' % self.name 2718 logging.debug('Parsing parameters for local tar %s, configuration ' 2719 'prefix is %s' % (self.name, config_prefix)) 2720 2721 self.path = self.params.get('%s_path' % config_prefix) 2722 logging.debug('Local source tar %s path: %s' % (self.name, 2723 self.path)) 2724 self.source = self.path 2725 self.destination = self.destination_dir 2726 2727 2728 class RemoteTarHelper(LocalTarHelper): 2729 ''' 2730 Helper that fetches a tarball and extracts it locally 2731 ''' 2732 def __init__(self, source_uri, destination_dir): 2733 self.source = source_uri 2734 self.destination = destination_dir 2735 2736 2737 def execute(self): 2738 ''' 2739 Executes all action this helper class is suposed to perform 2740 2741 This is the main entry point method for this class, and all other 2742 helper classes. 2743 2744 This implementation fetches the remote tar file and then extracts 2745 it using the functionality present in the parent class. 2746 ''' 2747 name = os.path.basename(self.source) 2748 base_dest = os.path.dirname(self.destination_dir) 2749 dest = os.path.join(base_dest, name) 2750 utils.get_file(self.source, dest) 2751 self.source = dest 2752 self.extract() 2753 2754 2755 class RemoteTarParamHelper(RemoteTarHelper): 2756 ''' 2757 Helps to deal with remote source tarballs specified in cartersian config 2758 2759 This class attempts to make it simple to manage a tarball with source code, 2760 by using a naming standard that follows this basic syntax: 2761 2762 <prefix>_name_<suffix> 2763 2764 <prefix> is always 'local_tar' and <suffix> sets options for this source 2765 tarball. Example for source tarball named foo: 2766 2767 remote_tar_foo_uri = http://foo.org/foo-1.0.tar.gz 2768 ''' 2769 def __init__(self, params, name, destination_dir): 2770 ''' 2771 Instantiates a new RemoteTarParamHelper instance 2772 ''' 2773 self.params = params 2774 self.name = name 2775 self.destination_dir = destination_dir 2776 self._parse_params() 2777 2778 2779 def _parse_params(self): 2780 ''' 2781 Parses the params items for entries related to this remote tar helper 2782 ''' 2783 config_prefix = 'remote_tar_%s' % self.name 2784 logging.debug('Parsing parameters for remote tar %s, configuration ' 2785 'prefix is %s' % (self.name, config_prefix)) 2786 2787 self.uri = self.params.get('%s_uri' % config_prefix) 2788 logging.debug('Remote source tar %s uri: %s' % (self.name, 2789 self.uri)) 2790 self.source = self.uri 2791 self.destination = self.destination_dir 2792 2793 2794 class PatchHelper(object): 2795 ''' 2796 Helper that encapsulates the patching of source code with patch files 2797 ''' 2798 def __init__(self, source_dir, patches): 2799 ''' 2800 Initializes a new PatchHelper 2801 ''' 2802 self.source_dir = source_dir 2803 self.patches = patches 2804 2805 2806 def download(self): 2807 ''' 2808 Copies patch files from remote locations to the source directory 2809 ''' 2810 for patch in self.patches: 2811 utils.get_file(patch, os.path.join(self.source_dir, 2812 os.path.basename(patch))) 2813 2814 2815 def patch(self): 2816 ''' 2817 Patches the source dir with all patch files 2818 ''' 2819 os.chdir(self.source_dir) 2820 for patch in self.patches: 2821 patch_file = os.path.join(self.source_dir, 2822 os.path.basename(patch)) 2823 utils.system('patch -p1 < %s' % os.path.basename(patch)) 2824 2825 2826 def execute(self): 2827 ''' 2828 Performs all steps necessary to download patches and apply them 2829 ''' 2830 self.download() 2831 self.patch() 2832 2833 2834 class PatchParamHelper(PatchHelper): 2835 ''' 2836 Helps to deal with patches specified in cartersian config files 2837 2838 This class attempts to make it simple to patch source coude, by using a 2839 naming standard that follows this basic syntax: 2840 2841 [<git_repo>|<local_src>|<local_tar>|<remote_tar>]_<name>_patches 2842 2843 <prefix> is either a 'local_src' or 'git_repo', that, together with <name> 2844 specify a directory containing source code to receive the patches. That is, 2845 for source code coming from git repo foo, patches would be specified as: 2846 2847 git_repo_foo_patches = ['http://foo/bar.patch', 'http://foo/baz.patch'] 2848 2849 And for for patches to be applied on local source code named also foo: 2850 2851 local_src_foo_patches = ['http://foo/bar.patch', 'http://foo/baz.patch'] 2852 ''' 2853 def __init__(self, params, prefix, source_dir): 2854 ''' 2855 Initializes a new PatchParamHelper instance 2856 ''' 2857 self.params = params 2858 self.prefix = prefix 2859 self.source_dir = source_dir 2860 self._parse_params() 2861 2862 2863 def _parse_params(self): 2864 ''' 2865 Parses the params items for entries related to this set of patches 2866 2867 This method currently does everything that the parent class __init__() 2868 method does, that is, sets all instance variables needed by other 2869 methods. That means it's not strictly necessary to call parent's 2870 __init__(). 2871 ''' 2872 logging.debug('Parsing patch parameters for prefix %s' % self.prefix) 2873 patches_param_key = '%s_patches' % self.prefix 2874 2875 self.patches_str = self.params.get(patches_param_key, '[]') 2876 logging.debug('Patches config for prefix %s: %s' % (self.prefix, 2877 self.patches_str)) 2878 2879 self.patches = eval(self.patches_str) 2880 logging.debug('Patches for prefix %s: %s' % (self.prefix, 2881 ", ".join(self.patches))) 2882 2883 2884 class GnuSourceBuildInvalidSource(Exception): 2885 ''' 2886 Exception raised when build source dir/file is not valid 2887 ''' 2888 pass 2889 2890 2891 class GnuSourceBuildHelper(object): 2892 ''' 2893 Handles software installation of GNU-like source code 2894 2895 This basically means that the build will go though the classic GNU 2896 autotools steps: ./configure, make, make install 2897 ''' 2898 def __init__(self, source, build_dir, prefix, 2899 configure_options=[]): 2900 ''' 2901 @type source: string 2902 @param source: source directory or tarball 2903 @type prefix: string 2904 @param prefix: installation prefix 2905 @type build_dir: string 2906 @param build_dir: temporary directory used for building the source code 2907 @type configure_options: list 2908 @param configure_options: options to pass to configure 2909 @throws: GnuSourceBuildInvalidSource 2910 ''' 2911 self.source = source 2912 self.build_dir = build_dir 2913 self.prefix = prefix 2914 self.configure_options = configure_options 2915 self.include_pkg_config_path() 2916 2917 2918 def include_pkg_config_path(self): 2919 ''' 2920 Adds the current prefix to the list of paths that pkg-config searches 2921 2922 This is currently not optional as there is no observed adverse side 2923 effects of enabling this. As the "prefix" is usually only valid during 2924 a test run, we believe that having other pkg-config files (*.pc) in 2925 either '<prefix>/share/pkgconfig' or '<prefix>/lib/pkgconfig' is 2926 exactly for the purpose of using them. 2927 2928 @returns: None 2929 ''' 2930 env_var = 'PKG_CONFIG_PATH' 2931 2932 include_paths = [os.path.join(self.prefix, 'share', 'pkgconfig'), 2933 os.path.join(self.prefix, 'lib', 'pkgconfig')] 2934 2935 if os.environ.has_key(env_var): 2936 paths = os.environ[env_var].split(':') 2937 for include_path in include_paths: 2938 if include_path not in paths: 2939 paths.append(include_path) 2940 os.environ[env_var] = ':'.join(paths) 2941 else: 2942 os.environ[env_var] = ':'.join(include_paths) 2943 2944 logging.debug('PKG_CONFIG_PATH is: %s' % os.environ['PKG_CONFIG_PATH']) 2945 2946 2947 def get_configure_path(self): 2948 ''' 2949 Checks if 'configure' exists, if not, return 'autogen.sh' as a fallback 2950 ''' 2951 configure_path = os.path.abspath(os.path.join(self.source, 2952 "configure")) 2953 autogen_path = os.path.abspath(os.path.join(self.source, 2954 "autogen.sh")) 2955 if os.path.exists(configure_path): 2956 return configure_path 2957 elif os.path.exists(autogen_path): 2958 return autogen_path 2959 else: 2960 raise GnuSourceBuildInvalidSource('configure script does not exist') 2961 2962 2963 def get_available_configure_options(self): 2964 ''' 2965 Return the list of available options of a GNU like configure script 2966 2967 This will run the "configure" script at the source directory 2968 2969 @returns: list of options accepted by configure script 2970 ''' 2971 help_raw = utils.system_output('%s --help' % self.get_configure_path(), 2972 ignore_status=True) 2973 help_output = help_raw.split("\n") 2974 option_list = [] 2975 for line in help_output: 2976 cleaned_line = line.lstrip() 2977 if cleaned_line.startswith("--"): 2978 option = cleaned_line.split()[0] 2979 option = option.split("=")[0] 2980 option_list.append(option) 2981 2982 return option_list 2983 2984 2985 def enable_debug_symbols(self): 2986 ''' 2987 Enables option that leaves debug symbols on compiled software 2988 2989 This makes debugging a lot easier. 2990 ''' 2991 enable_debug_option = "--disable-strip" 2992 if enable_debug_option in self.get_available_configure_options(): 2993 self.configure_options.append(enable_debug_option) 2994 logging.debug('Enabling debug symbols with option: %s' % 2995 enable_debug_option) 2996 2997 2998 def get_configure_command(self): 2999 ''' 3000 Formats configure script with all options set 3001 3002 @returns: string with all configure options, including prefix 3003 ''' 3004 prefix_option = "--prefix=%s" % self.prefix 3005 options = self.configure_options 3006 options.append(prefix_option) 3007 return "%s %s" % (self.get_configure_path(), 3008 " ".join(options)) 3009 3010 3011 def configure(self): 3012 ''' 3013 Runs the "configure" script passing apropriate command line options 3014 ''' 3015 configure_command = self.get_configure_command() 3016 logging.info('Running configure on build dir') 3017 os.chdir(self.build_dir) 3018 utils.system(configure_command) 3019 3020 3021 def make(self): 3022 ''' 3023 Runs "make" using the correct number of parallel jobs 3024 ''' 3025 parallel_make_jobs = utils.count_cpus() 3026 make_command = "make -j %s" % parallel_make_jobs 3027 logging.info("Running make on build dir") 3028 os.chdir(self.build_dir) 3029 utils.system(make_command) 3030 3031 3032 def make_install(self): 3033 ''' 3034 Runs "make install" 3035 ''' 3036 os.chdir(self.build_dir) 3037 utils.system("make install") 3038 3039 3040 install = make_install 3041 3042 3043 def execute(self): 3044 ''' 3045 Runs appropriate steps for *building* this source code tree 3046 ''' 3047 self.configure() 3048 self.make() 3049 3050 3051 class GnuSourceBuildParamHelper(GnuSourceBuildHelper): 3052 ''' 3053 Helps to deal with gnu_autotools build helper in cartersian config files 3054 3055 This class attempts to make it simple to build source coude, by using a 3056 naming standard that follows this basic syntax: 3057 3058 [<git_repo>|<local_src>]_<name>_<option> = value 3059 3060 To pass extra options to the configure script, while building foo from a 3061 git repo, set the following variable: 3062 3063 git_repo_foo_configure_options = --enable-feature 3064 ''' 3065 def __init__(self, params, name, destination_dir, install_prefix): 3066 ''' 3067 Instantiates a new GnuSourceBuildParamHelper 3068 ''' 3069 self.params = params 3070 self.name = name 3071 self.destination_dir = destination_dir 3072 self.install_prefix = install_prefix 3073 self._parse_params() 3074 3075 3076 def _parse_params(self): 3077 ''' 3078 Parses the params items for entries related to source directory 3079 3080 This method currently does everything that the parent class __init__() 3081 method does, that is, sets all instance variables needed by other 3082 methods. That means it's not strictly necessary to call parent's 3083 __init__(). 3084 ''' 3085 logging.debug('Parsing gnu_autotools build parameters for %s' % 3086 self.name) 3087 3088 configure_opt_key = '%s_configure_options' % self.name 3089 configure_options = self.params.get(configure_opt_key, '').split() 3090 logging.debug('Configure options for %s: %s' % (self.name, 3091 configure_options)) 3092 3093 self.source = self.destination_dir 3094 self.build_dir = self.destination_dir 3095 self.prefix = self.install_prefix 3096 self.configure_options = configure_options 3097 self.include_pkg_config_path() 3098 3099 3100 def install_host_kernel(job, params): 3101 """ 3102 Install a host kernel, given the appropriate params. 3103 3104 @param job: Job object. 3105 @param params: Dict with host kernel install params. 3106 """ 3107 install_type = params.get('host_kernel_install_type') 3108 3109 rpm_url = params.get('host_kernel_rpm_url') 3110 3111 koji_cmd = params.get('host_kernel_koji_cmd') 3112 koji_build = params.get('host_kernel_koji_build') 3113 koji_tag = params.get('host_kernel_koji_tag') 3114 3115 git_repo = params.get('host_kernel_git_repo') 3116 git_branch = params.get('host_kernel_git_branch') 3117 git_commit = params.get('host_kernel_git_commit') 3118 patch_list = params.get('host_kernel_patch_list') 3119 if patch_list: 3120 patch_list = patch_list.split() 3121 kernel_config = params.get('host_kernel_config') 3122 3123 if install_type == 'rpm': 3124 logging.info('Installing host kernel through rpm') 3125 dst = os.path.join("/tmp", os.path.basename(rpm_url)) 3126 k = utils.get_file(rpm_url, dst) 3127 host_kernel = job.kernel(k) 3128 host_kernel.install(install_vmlinux=False) 3129 host_kernel.boot() 3130 3131 elif install_type in ['koji', 'brew']: 3132 k_deps = KojiPkgSpec(tag=koji_tag, package='kernel', 3133 subpackages=['kernel-devel', 'kernel-firmware']) 3134 k = KojiPkgSpec(tag=koji_tag, package='kernel', 3135 subpackages=['kernel']) 3136 3137 c = KojiClient(koji_cmd) 3138 logging.info('Fetching kernel dependencies (-devel, -firmware)') 3139 c.get_pkgs(k_deps, job.tmpdir) 3140 logging.info('Installing kernel dependencies (-devel, -firmware) ' 3141 'through %s', install_type) 3142 k_deps_rpm_file_names = [os.path.join(job.tmpdir, rpm_file_name) for 3143 rpm_file_name in c.get_pkg_rpm_file_names(k_deps)] 3144 utils.run('rpm -U --force %s' % " ".join(k_deps_rpm_file_names)) 3145 3146 c.get_pkgs(k, job.tmpdir) 3147 k_rpm = os.path.join(job.tmpdir, 3148 c.get_pkg_rpm_file_names(k)[0]) 3149 host_kernel = job.kernel(k_rpm) 3150 host_kernel.install(install_vmlinux=False) 3151 host_kernel.boot() 3152 3153 elif install_type == 'git': 3154 logging.info('Chose to install host kernel through git, proceeding') 3155 repodir = os.path.join("/tmp", 'kernel_src') 3156 r = get_git_branch(git_repo, git_branch, repodir, git_commit) 3157 host_kernel = job.kernel(r) 3158 if patch_list: 3159 host_kernel.patch(patch_list) 3160 host_kernel.config(kernel_config) 3161 host_kernel.build() 3162 host_kernel.install() 3163 host_kernel.boot() 3164 3165 else: 3166 logging.info('Chose %s, using the current kernel for the host', 3167 install_type) 3168 3169 3170 def if_nametoindex(ifname): 3171 """ 3172 Map an interface name into its corresponding index. 3173 Returns 0 on error, as 0 is not a valid index 3174 3175 @param ifname: interface name 3176 """ 3177 index = 0 3178 ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) 3179 ifr = struct.pack("16si", ifname, 0) 3180 r = fcntl.ioctl(ctrl_sock, SIOCGIFINDEX, ifr) 3181 index = struct.unpack("16si", r)[1] 3182 ctrl_sock.close() 3183 return index 3184 3185 3186 def vnet_hdr_probe(tapfd): 3187 """ 3188 Check if the IFF_VNET_HDR is support by tun. 3189 3190 @param tapfd: the file descriptor of /dev/net/tun 3191 """ 3192 u = struct.pack("I", 0) 3193 try: 3194 r = fcntl.ioctl(tapfd, TUNGETFEATURES, u) 3195 except OverflowError: 3196 return False 3197 flags = struct.unpack("I", r)[0] 3198 if flags & IFF_VNET_HDR: 3199 return True 3200 else: 3201 return False 3202 3203 3204 def open_tap(devname, ifname, vnet_hdr=True): 3205 """ 3206 Open a tap device and returns its file descriptor which is used by 3207 fd=<fd> parameter of qemu-kvm. 3208 3209 @param ifname: TAP interface name 3210 @param vnet_hdr: Whether enable the vnet header 3211 """ 3212 try: 3213 tapfd = os.open(devname, os.O_RDWR) 3214 except OSError, e: 3215 raise TAPModuleError(devname, "open", e) 3216 flags = IFF_TAP | IFF_NO_PI 3217 if vnet_hdr and vnet_hdr_probe(tapfd): 3218 flags |= IFF_VNET_HDR 3219 3220 ifr = struct.pack("16sh", ifname, flags) 3221 try: 3222 r = fcntl.ioctl(tapfd, TUNSETIFF, ifr) 3223 except IOError, details: 3224 raise TAPCreationError(ifname, details) 3225 ifname = struct.unpack("16sh", r)[0].strip("\x00") 3226 return tapfd 3227 3228 3229 def add_to_bridge(ifname, brname): 3230 """ 3231 Add a TAP device to bridge 3232 3233 @param ifname: Name of TAP device 3234 @param brname: Name of the bridge 3235 """ 3236 ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) 3237 index = if_nametoindex(ifname) 3238 if index == 0: 3239 raise TAPNotExistError(ifname) 3240 ifr = struct.pack("16si", brname, index) 3241 try: 3242 r = fcntl.ioctl(ctrl_sock, SIOCBRADDIF, ifr) 3243 except IOError, details: 3244 raise BRAddIfError(ifname, brname, details) 3245 ctrl_sock.close() 3246 3247 3248 def bring_up_ifname(ifname): 3249 """ 3250 Bring up an interface 3251 3252 @param ifname: Name of the interface 3253 """ 3254 ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) 3255 ifr = struct.pack("16si", ifname, IFF_UP) 3256 try: 3257 fcntl.ioctl(ctrl_sock, SIOCSIFFLAGS, ifr) 3258 except IOError: 3259 raise TAPBringUpError(ifname) 3260 ctrl_sock.close() 3261 3262 3263 def if_set_macaddress(ifname, mac): 3264 """ 3265 Set the mac address for an interface 3266 3267 @param ifname: Name of the interface 3268 @mac: Mac address 3269 """ 3270 ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) 3271 3272 ifr = struct.pack("256s", ifname) 3273 try: 3274 mac_dev = fcntl.ioctl(ctrl_sock, SIOCGIFHWADDR, ifr)[18:24] 3275 mac_dev = ":".join(["%02x" % ord(m) for m in mac_dev]) 3276 except IOError, e: 3277 raise HwAddrGetError(ifname) 3278 3279 if mac_dev.lower() == mac.lower(): 3280 return 3281 3282 ifr = struct.pack("16sH14s", ifname, 1, 3283 "".join([chr(int(m, 16)) for m in mac.split(":")])) 3284 try: 3285 fcntl.ioctl(ctrl_sock, SIOCSIFHWADDR, ifr) 3286 except IOError, e: 3287 logging.info(e) 3288 raise HwAddrSetError(ifname, mac) 3289 ctrl_sock.close() 3290