Home | History | Annotate | Download | only in virt
      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