Home | History | Annotate | Download | only in virt
      1 import os, time, commands, re, logging, glob, threading, shutil
      2 from autotest_lib.client.bin import utils
      3 from autotest_lib.client.common_lib import error
      4 import aexpect, virt_utils, kvm_monitor, ppm_utils, virt_test_setup
      5 import virt_vm, kvm_vm
      6 try:
      7     import PIL.Image
      8 except ImportError:
      9     logging.warning('No python imaging library installed. PPM image '
     10                     'conversion to JPEG disabled. In order to enable it, '
     11                     'please install python-imaging or the equivalent for your '
     12                     'distro.')
     13 
     14 
     15 _screendump_thread = None
     16 _screendump_thread_termination_event = None
     17 
     18 
     19 def preprocess_image(test, params):
     20     """
     21     Preprocess a single QEMU image according to the instructions in params.
     22 
     23     @param test: Autotest test object.
     24     @param params: A dict containing image preprocessing parameters.
     25     @note: Currently this function just creates an image if requested.
     26     """
     27     image_filename = virt_vm.get_image_filename(params, test.bindir)
     28 
     29     create_image = False
     30 
     31     if params.get("force_create_image") == "yes":
     32         logging.debug("Param 'force_create_image' specified, creating image")
     33         create_image = True
     34     elif (params.get("create_image") == "yes" and not
     35           os.path.exists(image_filename)):
     36         create_image = True
     37 
     38     if create_image and not virt_vm.create_image(params, test.bindir):
     39         raise error.TestError("Could not create image")
     40 
     41 
     42 def preprocess_vm(test, params, env, name):
     43     """
     44     Preprocess a single VM object according to the instructions in params.
     45     Start the VM if requested and get a screendump.
     46 
     47     @param test: An Autotest test object.
     48     @param params: A dict containing VM preprocessing parameters.
     49     @param env: The environment (a dict-like object).
     50     @param name: The name of the VM object.
     51     """
     52     logging.debug("Preprocessing VM '%s'", name)
     53     vm = env.get_vm(name)
     54     if not vm:
     55         logging.debug("VM object for '%s' does not exist, creating it", name)
     56         vm_type = params.get('vm_type')
     57         if vm_type == 'kvm':
     58             vm = kvm_vm.VM(name, params, test.bindir, env.get("address_cache"))
     59         env.register_vm(name, vm)
     60 
     61     start_vm = False
     62 
     63     if params.get("restart_vm") == "yes":
     64         logging.debug("Param 'restart_vm' specified, (re)starting VM")
     65         start_vm = True
     66     elif params.get("migration_mode"):
     67         logging.debug("Param 'migration_mode' specified, starting VM in "
     68                       "incoming migration mode")
     69         start_vm = True
     70     elif params.get("start_vm") == "yes":
     71         if not vm.is_alive():
     72             logging.debug("VM is not alive, starting it")
     73             start_vm = True
     74         if vm.needs_restart(name=name, params=params, basedir=test.bindir):
     75             logging.debug("Current VM specs differ from requested one; "
     76                           "restarting it")
     77             start_vm = True
     78 
     79     if start_vm:
     80         # Start the VM (or restart it if it's already up)
     81         vm.create(name, params, test.bindir,
     82                   migration_mode=params.get("migration_mode"))
     83     else:
     84         # Don't start the VM, just update its params
     85         vm.params = params
     86 
     87     scrdump_filename = os.path.join(test.debugdir, "pre_%s.ppm" % name)
     88     try:
     89         if vm.monitor and params.get("take_regular_screendumps") == "yes":
     90             vm.monitor.screendump(scrdump_filename, debug=False)
     91     except kvm_monitor.MonitorError, e:
     92         logging.warning(e)
     93 
     94 
     95 def postprocess_image(test, params):
     96     """
     97     Postprocess a single QEMU image according to the instructions in params.
     98 
     99     @param test: An Autotest test object.
    100     @param params: A dict containing image postprocessing parameters.
    101     """
    102     if params.get("check_image") == "yes":
    103         virt_vm.check_image(params, test.bindir)
    104     if params.get("remove_image") == "yes":
    105         virt_vm.remove_image(params, test.bindir)
    106 
    107 
    108 def postprocess_vm(test, params, env, name):
    109     """
    110     Postprocess a single VM object according to the instructions in params.
    111     Kill the VM if requested and get a screendump.
    112 
    113     @param test: An Autotest test object.
    114     @param params: A dict containing VM postprocessing parameters.
    115     @param env: The environment (a dict-like object).
    116     @param name: The name of the VM object.
    117     """
    118     logging.debug("Postprocessing VM '%s'" % name)
    119     vm = env.get_vm(name)
    120     if not vm:
    121         return
    122 
    123     scrdump_filename = os.path.join(test.debugdir, "post_%s.ppm" % name)
    124     try:
    125         if vm.monitor and params.get("take_regular_screenshots") == "yes":
    126             vm.monitor.screendump(scrdump_filename, debug=False)
    127     except kvm_monitor.MonitorError, e:
    128         logging.warning(e)
    129 
    130     if params.get("kill_vm") == "yes":
    131         kill_vm_timeout = float(params.get("kill_vm_timeout", 0))
    132         if kill_vm_timeout:
    133             logging.debug("Param 'kill_vm' specified, waiting for VM to shut "
    134                           "down before killing it")
    135             virt_utils.wait_for(vm.is_dead, kill_vm_timeout, 0, 1)
    136         else:
    137             logging.debug("Param 'kill_vm' specified, killing VM")
    138         vm.destroy(gracefully = params.get("kill_vm_gracefully") == "yes")
    139 
    140 
    141 def process_command(test, params, env, command, command_timeout,
    142                     command_noncritical):
    143     """
    144     Pre- or post- custom commands to be executed before/after a test is run
    145 
    146     @param test: An Autotest test object.
    147     @param params: A dict containing all VM and image parameters.
    148     @param env: The environment (a dict-like object).
    149     @param command: Command to be run.
    150     @param command_timeout: Timeout for command execution.
    151     @param command_noncritical: If True test will not fail if command fails.
    152     """
    153     # Export environment vars
    154     for k in params:
    155         os.putenv("KVM_TEST_%s" % k, str(params[k]))
    156     # Execute commands
    157     try:
    158         utils.system("cd %s; %s" % (test.bindir, command))
    159     except error.CmdError, e:
    160         if command_noncritical:
    161             logging.warning(e)
    162         else:
    163             raise
    164 
    165 
    166 def process(test, params, env, image_func, vm_func, vm_first=False):
    167     """
    168     Pre- or post-process VMs and images according to the instructions in params.
    169     Call image_func for each image listed in params and vm_func for each VM.
    170 
    171     @param test: An Autotest test object.
    172     @param params: A dict containing all VM and image parameters.
    173     @param env: The environment (a dict-like object).
    174     @param image_func: A function to call for each image.
    175     @param vm_func: A function to call for each VM.
    176     """
    177     # Get list of VMs specified for this test
    178     for vm_name in params.objects("vms"):
    179         vm_params = params.object_params(vm_name)
    180         if not vm_first:
    181             # Get list of images specified for this VM
    182             for image_name in vm_params.objects("images"):
    183                 image_params = vm_params.object_params(image_name)
    184                 # Call image_func for each image
    185                 image_func(test, image_params)
    186             # Call vm_func for each vm
    187             vm_func(test, vm_params, env, vm_name)
    188         else:
    189             vm_func(test, vm_params, env, vm_name)
    190             for image_name in vm_params.objects("images"):
    191                 image_params = vm_params.object_params(image_name)
    192                 image_func(test, image_params)
    193 
    194 
    195 
    196 @error.context_aware
    197 def preprocess(test, params, env):
    198     """
    199     Preprocess all VMs and images according to the instructions in params.
    200     Also, collect some host information, such as the KVM version.
    201 
    202     @param test: An Autotest test object.
    203     @param params: A dict containing all VM and image parameters.
    204     @param env: The environment (a dict-like object).
    205     """
    206     error.context("preprocessing")
    207 
    208     # Start tcpdump if it isn't already running
    209     if "address_cache" not in env:
    210         env["address_cache"] = {}
    211     if "tcpdump" in env and not env["tcpdump"].is_alive():
    212         env["tcpdump"].close()
    213         del env["tcpdump"]
    214     if "tcpdump" not in env and params.get("run_tcpdump", "yes") == "yes":
    215         cmd = "%s -npvi any 'dst port 68'" % virt_utils.find_command("tcpdump")
    216         logging.debug("Starting tcpdump '%s'", cmd)
    217         env["tcpdump"] = aexpect.Tail(
    218             command=cmd,
    219             output_func=_update_address_cache,
    220             output_params=(env["address_cache"],))
    221         if virt_utils.wait_for(lambda: not env["tcpdump"].is_alive(),
    222                               0.1, 0.1, 1.0):
    223             logging.warning("Could not start tcpdump")
    224             logging.warning("Status: %s" % env["tcpdump"].get_status())
    225             logging.warning("Output:" + virt_utils.format_str_for_message(
    226                 env["tcpdump"].get_output()))
    227 
    228     # Destroy and remove VMs that are no longer needed in the environment
    229     requested_vms = params.objects("vms")
    230     for key in env.keys():
    231         vm = env[key]
    232         if not virt_utils.is_vm(vm):
    233             continue
    234         if not vm.name in requested_vms:
    235             logging.debug("VM '%s' found in environment but not required for "
    236                           "test, destroying it" % vm.name)
    237             vm.destroy()
    238             del env[key]
    239 
    240     # Get the KVM kernel module version and write it as a keyval
    241     if os.path.exists("/dev/kvm"):
    242         try:
    243             kvm_version = open("/sys/module/kvm/version").read().strip()
    244         except:
    245             kvm_version = os.uname()[2]
    246     else:
    247         kvm_version = "Unknown"
    248         logging.debug("KVM module not loaded")
    249     logging.debug("KVM version: %s" % kvm_version)
    250     test.write_test_keyval({"kvm_version": kvm_version})
    251 
    252     # Get the KVM userspace version and write it as a keyval
    253     qemu_path = virt_utils.get_path(test.bindir, params.get("qemu_binary",
    254                                                            "qemu"))
    255     version_line = commands.getoutput("%s -help | head -n 1" % qemu_path)
    256     matches = re.findall("[Vv]ersion .*?,", version_line)
    257     if matches:
    258         kvm_userspace_version = " ".join(matches[0].split()[1:]).strip(",")
    259     else:
    260         kvm_userspace_version = "Unknown"
    261     logging.debug("KVM userspace version: %s" % kvm_userspace_version)
    262     test.write_test_keyval({"kvm_userspace_version": kvm_userspace_version})
    263 
    264     if params.get("setup_hugepages") == "yes":
    265         h = virt_test_setup.HugePageConfig(params)
    266         h.setup()
    267 
    268     # Execute any pre_commands
    269     if params.get("pre_command"):
    270         process_command(test, params, env, params.get("pre_command"),
    271                         int(params.get("pre_command_timeout", "600")),
    272                         params.get("pre_command_noncritical") == "yes")
    273 
    274     # Preprocess all VMs and images
    275     process(test, params, env, preprocess_image, preprocess_vm)
    276 
    277     # Start the screendump thread
    278     if params.get("take_regular_screendumps") == "yes":
    279         logging.debug("Starting screendump thread")
    280         global _screendump_thread, _screendump_thread_termination_event
    281         _screendump_thread_termination_event = threading.Event()
    282         _screendump_thread = threading.Thread(target=_take_screendumps,
    283                                               args=(test, params, env))
    284         _screendump_thread.start()
    285 
    286 
    287 @error.context_aware
    288 def postprocess(test, params, env):
    289     """
    290     Postprocess all VMs and images according to the instructions in params.
    291 
    292     @param test: An Autotest test object.
    293     @param params: Dict containing all VM and image parameters.
    294     @param env: The environment (a dict-like object).
    295     """
    296     error.context("postprocessing")
    297 
    298     # Postprocess all VMs and images
    299     process(test, params, env, postprocess_image, postprocess_vm, vm_first=True)
    300 
    301     # Terminate the screendump thread
    302     global _screendump_thread, _screendump_thread_termination_event
    303     if _screendump_thread:
    304         logging.debug("Terminating screendump thread")
    305         _screendump_thread_termination_event.set()
    306         _screendump_thread.join(10)
    307         _screendump_thread = None
    308 
    309     # Warn about corrupt PPM files
    310     for f in glob.glob(os.path.join(test.debugdir, "*.ppm")):
    311         if not ppm_utils.image_verify_ppm_file(f):
    312             logging.warning("Found corrupt PPM file: %s", f)
    313 
    314     # Should we convert PPM files to PNG format?
    315     if params.get("convert_ppm_files_to_png") == "yes":
    316         logging.debug("Param 'convert_ppm_files_to_png' specified, converting "
    317                       "PPM files to PNG format")
    318         try:
    319             for f in glob.glob(os.path.join(test.debugdir, "*.ppm")):
    320                 if ppm_utils.image_verify_ppm_file(f):
    321                     new_path = f.replace(".ppm", ".png")
    322                     image = PIL.Image.open(f)
    323                     image.save(new_path, format='PNG')
    324         except NameError:
    325             pass
    326 
    327     # Should we keep the PPM files?
    328     if params.get("keep_ppm_files") != "yes":
    329         logging.debug("Param 'keep_ppm_files' not specified, removing all PPM "
    330                       "files from debug dir")
    331         for f in glob.glob(os.path.join(test.debugdir, '*.ppm')):
    332             os.unlink(f)
    333 
    334     # Should we keep the screendump dirs?
    335     if params.get("keep_screendumps") != "yes":
    336         logging.debug("Param 'keep_screendumps' not specified, removing "
    337                       "screendump dirs")
    338         for d in glob.glob(os.path.join(test.debugdir, "screendumps_*")):
    339             if os.path.isdir(d) and not os.path.islink(d):
    340                 shutil.rmtree(d, ignore_errors=True)
    341 
    342     # Kill all unresponsive VMs
    343     if params.get("kill_unresponsive_vms") == "yes":
    344         logging.debug("Param 'kill_unresponsive_vms' specified, killing all "
    345                       "VMs that fail to respond to a remote login request")
    346         for vm in env.get_all_vms():
    347             if vm.is_alive():
    348                 try:
    349                     session = vm.login()
    350                     session.close()
    351                 except (virt_utils.LoginError, virt_vm.VMError), e:
    352                     logging.warning(e)
    353                     vm.destroy(gracefully=False)
    354 
    355     # Kill all aexpect tail threads
    356     aexpect.kill_tail_threads()
    357 
    358     # Terminate tcpdump if no VMs are alive
    359     living_vms = [vm for vm in env.get_all_vms() if vm.is_alive()]
    360     if not living_vms and "tcpdump" in env:
    361         env["tcpdump"].close()
    362         del env["tcpdump"]
    363 
    364     if params.get("setup_hugepages") == "yes":
    365         h = virt_test_setup.HugePageConfig(params)
    366         h.cleanup()
    367 
    368     # Execute any post_commands
    369     if params.get("post_command"):
    370         process_command(test, params, env, params.get("post_command"),
    371                         int(params.get("post_command_timeout", "600")),
    372                         params.get("post_command_noncritical") == "yes")
    373 
    374 
    375 def postprocess_on_error(test, params, env):
    376     """
    377     Perform postprocessing operations required only if the test failed.
    378 
    379     @param test: An Autotest test object.
    380     @param params: A dict containing all VM and image parameters.
    381     @param env: The environment (a dict-like object).
    382     """
    383     params.update(params.object_params("on_error"))
    384 
    385 
    386 def _update_address_cache(address_cache, line):
    387     if re.search("Your.IP", line, re.IGNORECASE):
    388         matches = re.findall(r"\d*\.\d*\.\d*\.\d*", line)
    389         if matches:
    390             address_cache["last_seen"] = matches[0]
    391     if re.search("Client.Ethernet.Address", line, re.IGNORECASE):
    392         matches = re.findall(r"\w*:\w*:\w*:\w*:\w*:\w*", line)
    393         if matches and address_cache.get("last_seen"):
    394             mac_address = matches[0].lower()
    395             if time.time() - address_cache.get("time_%s" % mac_address, 0) > 5:
    396                 logging.debug("(address cache) Adding cache entry: %s ---> %s",
    397                               mac_address, address_cache.get("last_seen"))
    398             address_cache[mac_address] = address_cache.get("last_seen")
    399             address_cache["time_%s" % mac_address] = time.time()
    400             del address_cache["last_seen"]
    401 
    402 
    403 def _take_screendumps(test, params, env):
    404     global _screendump_thread_termination_event
    405     temp_dir = test.debugdir
    406     if params.get("screendump_temp_dir"):
    407         temp_dir = virt_utils.get_path(test.bindir,
    408                                       params.get("screendump_temp_dir"))
    409         try:
    410             os.makedirs(temp_dir)
    411         except OSError:
    412             pass
    413     temp_filename = os.path.join(temp_dir, "scrdump-%s.ppm" %
    414                                  virt_utils.generate_random_string(6))
    415     delay = float(params.get("screendump_delay", 5))
    416     quality = int(params.get("screendump_quality", 30))
    417 
    418     cache = {}
    419 
    420     while True:
    421         for vm in env.get_all_vms():
    422             if not vm.is_alive():
    423                 continue
    424             try:
    425                 vm.monitor.screendump(filename=temp_filename, debug=False)
    426             except kvm_monitor.MonitorError, e:
    427                 logging.warning(e)
    428                 continue
    429             except AttributeError, e:
    430                 continue
    431             if not os.path.exists(temp_filename):
    432                 logging.warning("VM '%s' failed to produce a screendump", vm.name)
    433                 continue
    434             if not ppm_utils.image_verify_ppm_file(temp_filename):
    435                 logging.warning("VM '%s' produced an invalid screendump", vm.name)
    436                 os.unlink(temp_filename)
    437                 continue
    438             screendump_dir = os.path.join(test.debugdir,
    439                                           "screendumps_%s" % vm.name)
    440             try:
    441                 os.makedirs(screendump_dir)
    442             except OSError:
    443                 pass
    444             screendump_filename = os.path.join(screendump_dir,
    445                     "%s_%s.jpg" % (vm.name,
    446                                    time.strftime("%Y-%m-%d_%H-%M-%S")))
    447             hash = utils.hash_file(temp_filename)
    448             if hash in cache:
    449                 try:
    450                     os.link(cache[hash], screendump_filename)
    451                 except OSError:
    452                     pass
    453             else:
    454                 try:
    455                     image = PIL.Image.open(temp_filename)
    456                     image.save(screendump_filename, format="JPEG", quality=quality)
    457                     cache[hash] = screendump_filename
    458                 except NameError:
    459                     pass
    460             os.unlink(temp_filename)
    461         if _screendump_thread_termination_event.isSet():
    462             _screendump_thread_termination_event = None
    463             break
    464         _screendump_thread_termination_event.wait(delay)
    465