Adding kvm test

The KVM team has been working on a set of tests for our virtualization
stack based on autotest. The project is known as kvm-autotest:

http://www.linux-kvm.org/page/KVM-Autotest

git://git.kernel.org/pub/scm/virt/kvm/kvm-autotest.git

In order to accomodate the needs of KVM Quality Engineering, quite a
substantial amount of code was written. A very brief overview of how the
tests are structured follows:

* kvm_runtest_2: Entry point, that defines hooks to all the KVM tests,
documented under:

http://www.linux-kvm.org/page/KVM-Autotest/Tests

* kvm_config: Module that handles the KVM configuration file format

http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File

It parses the configuration file and generates a list of dictionaries
that will be used by the other tests.

* step editor and stepmaker: A method to automate installation of guests
was devised, the idea is to send input to qemu through *step files*.
StepEditor and StepMaker are pygtk applications that allow to create and
edit step files.

From:
uril@redhat.com (Uri Lublin)
mgoldish@redhat.com (Michael Goldish)
dhuff@redhat.com (David Huff)
aeromenk@redhat.com (Alexey Eromenko)
mburns@redhat.com (Mike Burns)

Signed-off-by: Lucas Meneghel Rodrigues <lmr@redhat.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@3187 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/client/tests/kvm/kvm_preprocessing.py b/client/tests/kvm/kvm_preprocessing.py
new file mode 100644
index 0000000..e395d98
--- /dev/null
+++ b/client/tests/kvm/kvm_preprocessing.py
@@ -0,0 +1,255 @@
+import sys, os, time, commands, re, logging
+from autotest_lib.client.bin import test
+from autotest_lib.client.common_lib import error
+import kvm_vm, kvm_utils
+
+
+def preprocess_image(test, params):
+    """
+    Preprocess a single QEMU image according to the instructions in params.
+
+    @param test: Autotest test object.
+    @param params: A dict containing image preprocessing parameters.
+    @note: Currently this function just creates an image if requested.
+    """
+    qemu_img_path = os.path.join(test.bindir, "qemu-img")
+    image_dir = os.path.join(test.bindir, "images")
+    image_filename = kvm_vm.get_image_filename(params, image_dir)
+
+    create_image = False
+
+    if params.get("force_create_image") == "yes":
+        logging.debug("'force_create_image' specified; creating image...")
+        create_image = True
+    elif params.get("create_image") == "yes" and not \
+    os.path.exists(image_filename):
+        logging.debug("Creating image...")
+        create_image = True
+
+    if create_image:
+        if not kvm_vm.create_image(params, qemu_img_path, image_dir):
+            message = "Could not create image"
+            logging.error(message)
+            raise error.TestError(message)
+
+
+def preprocess_vm(test, params, env, name):
+    """
+    Preprocess a single VM object according to the instructions in params.
+    Start the VM if requested and get a screendump.
+
+    @param test: An Autotest test object.
+    @param params: A dict containing VM preprocessing parameters.
+    @param env: The environment (a dict-like object).
+    @param name: The name of the VM object.
+    """
+    qemu_path = os.path.join(test.bindir, "qemu")
+    image_dir = os.path.join(test.bindir, "images")
+    iso_dir = os.path.join(test.bindir, "isos")
+
+    logging.debug("Preprocessing VM '%s'..." % name)
+    vm = kvm_utils.env_get_vm(env, name)
+    if vm:
+        logging.debug("VM object found in environment")
+    else:
+        logging.debug("VM object does not exist; creating it")
+        vm = kvm_vm.VM(name, params, qemu_path, image_dir, iso_dir)
+        kvm_utils.env_register_vm(env, name, vm)
+
+    start_vm = False
+    for_migration = False
+
+    if params.get("start_vm_for_migration") == "yes":
+        logging.debug("'start_vm_for_migration' specified; (re)starting VM with"
+                      " -incoming option...")
+        start_vm = True
+        for_migration = True
+    elif params.get("restart_vm") == "yes":
+        logging.debug("'restart_vm' specified; (re)starting VM...")
+        start_vm = True
+    elif params.get("start_vm") == "yes":
+        if not vm.is_alive():
+            logging.debug("VM is not alive; starting it...")
+            start_vm = True
+        elif vm.make_qemu_command() != vm.make_qemu_command(name, params,
+                                                            qemu_path,
+                                                            image_dir,
+                                                            iso_dir):
+            logging.debug("VM's qemu command differs from requested one;"
+                          "restarting it...")
+            start_vm = True
+
+    if start_vm:
+        vm.destroy()
+        if not vm.create(name, params, qemu_path, image_dir, iso_dir,
+                         for_migration):
+            message = "Could not start VM"
+            logging.error(message)
+            raise error.TestError(message)
+
+    scrdump_filename = os.path.join(test.debugdir, "pre_%s.ppm" % name)
+    vm.send_monitor_cmd("screendump %s" % scrdump_filename)
+
+
+def postprocess_image(test, params):
+    """
+    Postprocess a single QEMU image according to the instructions in params.
+    Currently this function just removes an image if requested.
+
+    @param test: An Autotest test object.
+    @param params: A dict containing image postprocessing parameters.
+    """
+    image_dir = os.path.join(test.bindir, "images")
+
+    if params.get("remove_image") == "yes":
+        kvm_vm.remove_image(params, image_dir)
+
+
+def postprocess_vm(test, params, env, name):
+    """
+    Postprocess a single VM object according to the instructions in params.
+    Kill the VM if requested and get a screendump.
+
+    @param test: An Autotest test object.
+    @param params: A dict containing VM postprocessing parameters.
+    @param env: The environment (a dict-like object).
+    @param name: The name of the VM object.
+    """
+    logging.debug("Postprocessing VM '%s'..." % name)
+    vm = kvm_utils.env_get_vm(env, name)
+    if vm:
+        logging.debug("VM object found in environment")
+    else:
+        logging.debug("VM object does not exist in environment")
+        return
+
+    scrdump_filename = os.path.join(test.debugdir, "post_%s.ppm" % name)
+    vm.send_monitor_cmd("screendump %s" % scrdump_filename)
+
+    if params.get("kill_vm") == "yes":
+        if not kvm_utils.wait_for(vm.is_dead,
+                float(params.get("kill_vm_timeout", 0)), 0.0, 1.0,
+                "Waiting for VM to kill itself..."):
+            logging.debug("'kill_vm' specified; killing VM...")
+        vm.destroy(gracefully = params.get("kill_vm_gracefully") == "yes")
+
+
+def process(test, params, env, image_func, vm_func):
+    """
+    Pre- or post-process VMs and images according to the instructions in params.
+    Call image_func for each image listed in params and vm_func for each VM.
+
+    @param test: An Autotest test object.
+    @param params: A dict containing all VM and image parameters.
+    @param env: The environment (a dict-like object).
+    @param image_func: A function to call for each image.
+    @param vm_func: A function to call for each VM.
+    """
+    # Get list of VMs specified for this test
+    vm_names = kvm_utils.get_sub_dict_names(params, "vms")
+    for vm_name in vm_names:
+        vm_params = kvm_utils.get_sub_dict(params, vm_name)
+        # Get list of images specified for this VM
+        image_names = kvm_utils.get_sub_dict_names(vm_params, "images")
+        for image_name in image_names:
+            image_params = kvm_utils.get_sub_dict(vm_params, image_name)
+            # Call image_func for each image
+            image_func(test, image_params)
+        # Call vm_func for each vm
+        vm_func(test, vm_params, env, vm_name)
+
+
+def preprocess(test, params, env):
+    """
+    Preprocess all VMs and images according to the instructions in params.
+    Also, collect some host information, such as the KVM version.
+
+    @param test: An Autotest test object.
+    @param params: A dict containing all VM and image parameters.
+    @param env: The environment (a dict-like object).
+    """
+    # Verify the identities of all living VMs
+    for vm in env.values():
+        if not kvm_utils.is_vm(vm):
+            continue
+        if vm.is_dead():
+            continue
+        if not vm.verify_process_identity():
+            logging.debug("VM '%s' seems to have been replaced by another"
+                          " process" % vm.name)
+            vm.pid = None
+
+    # Destroy and remove VMs that are no longer needed in the environment
+    requested_vms = kvm_utils.get_sub_dict_names(params, "vms")
+    for key in env.keys():
+        vm = env[key]
+        if not kvm_utils.is_vm(vm):
+            continue
+        if not vm.name in requested_vms:
+            logging.debug("VM '%s' found in environment but not required for"
+                          " test; removing it..." % vm.name)
+            vm.destroy()
+            del env[key]
+
+    # Preprocess all VMs and images
+    process(test, params, env, preprocess_image, preprocess_vm)
+
+    # Get the KVM kernel module version and write it as a keyval
+    logging.debug("Fetching KVM module version...")
+    if os.path.exists("/dev/kvm"):
+        kvm_version = os.uname()[2]
+        try:
+            file = open("/sys/module/kvm/version", "r")
+            kvm_version = file.read().strip()
+            file.close()
+        except:
+            pass
+    else:
+        kvm_version = "Unknown"
+        logging.debug("KVM module not loaded")
+    logging.debug("KVM version: %s" % kvm_version)
+    test.write_test_keyval({"kvm_version": kvm_version})
+
+    # Get the KVM userspace version and write it as a keyval
+    logging.debug("Fetching KVM userspace version...")
+    qemu_path = os.path.join(test.bindir, "qemu")
+    version_line = commands.getoutput("%s -help | head -n 1" % qemu_path)
+    exp = re.compile("[Vv]ersion .*?,")
+    match = exp.search(version_line)
+    if match:
+        kvm_userspace_version = " ".join(match.group().split()[1:]).strip(",")
+    else:
+        kvm_userspace_version = "Unknown"
+        logging.debug("Could not fetch KVM userspace version")
+    logging.debug("KVM userspace version: %s" % kvm_userspace_version)
+    test.write_test_keyval({"kvm_userspace_version": kvm_userspace_version})
+
+
+def postprocess(test, params, env):
+    """
+    Postprocess all VMs and images according to the instructions in params.
+
+    @param test: An Autotest test object.
+    @param params: Dict containing all VM and image parameters.
+    @param env: The environment (a dict-like object).
+    """
+    process(test, params, env, postprocess_image, postprocess_vm)
+
+    # See if we should get rid of all PPM files
+    if not params.get("keep_ppm_files") == "yes":
+        # Remove them all
+        logging.debug("'keep_ppm_files' not specified; removing all PPM files"
+                      " from results dir...")
+        kvm_utils.run_bg("rm -vf %s" % os.path.join(test.debugdir, "*.ppm"),
+                          None, logging.debug, "(rm) ", timeout=5.0)
+
+
+def postprocess_on_error(test, params, env):
+    """
+    Perform postprocessing operations required only if the test failed.
+
+    @param test: An Autotest test object.
+    @param params: A dict containing all VM and image parameters.
+    @param env: The environment (a dict-like object).
+    """
+    params.update(kvm_utils.get_sub_dict(params, "on_error"))