diff mbox

[2/2] xtf: add a launcher script

Message ID 1463665617-21781-3-git-send-email-roger.pau@citrix.com (mailing list archive)
State New, archived
Headers show

Commit Message

Roger Pau Monné May 19, 2016, 1:46 p.m. UTC
Add a simple script that can list the tests relevant to the current
environment and run them. In it's current form it's functionality is quite
limited, and consists in one of this two options:

 - list: list tests relevant to the current environment. This information is
   fetched from each test test-info.json and the output of `xl info`.
 - test: launch a test (which can include creating several VMs) and report
   the results back. The results are printed on the terminal, and the error
   code is set appropriately (see backend/return_code.py for the list of
   possible error codes).

The path to the directory where the tests are stored is mandatory, and
should always be passed to the script. As an example, I've used the
following bash runes in order to launch all tests suitable for my
environment:

for test in `dist/bin/xtf-launcher.py -f dist/tests/ -l`
do
dist/bin/xtf-launcher.py -f dist/tests/ -t $test
echo "Test $test return code: $?"
done

Which yields the following (trimmed for convenience):

Parsing config from dist/tests/cpuid/test-pv64-cpuid.cfg
test-pv64-cpuid took 0s
Parsing config from dist/tests/cpuid/test-pv32pae-cpuid.cfg
test-pv32pae-cpuid took 0s
Parsing config from dist/tests/cpuid/test-hvm64-cpuid.cfg
libxl: warning: libxl_dm.c:1486:libxl__build_device_model_args_new: Could not find user xen-qemuuser-shared, starting QEMU as root
test-hvm64-cpuid took 0s
Parsing config from dist/tests/cpuid/test-hvm32pae-cpuid.cfg
libxl: warning: libxl_dm.c:1486:libxl__build_device_model_args_new: Could not find user xen-qemuuser-shared, starting QEMU as root
test-hvm32pae-cpuid took 0s
Parsing config from dist/tests/cpuid/test-hvm32pse-cpuid.cfg
libxl: warning: libxl_dm.c:1486:libxl__build_device_model_args_new: Could not find user xen-qemuuser-shared, starting QEMU as root
test-hvm32pse-cpuid took 0s
Parsing config from dist/tests/cpuid/test-hvm32-cpuid.cfg
libxl: warning: libxl_dm.c:1486:libxl__build_device_model_args_new: Could not find user xen-qemuuser-shared, starting QEMU as root
test-hvm32-cpuid took 0s
test-pv64-cpuid:	SUCCESS
test-hvm64-cpuid:	SUCCESS
test-hvm32-cpuid:	SUCCESS
test-hvm32pse-cpuid:	SUCCESS
test-hvm32pae-cpuid:	SUCCESS
test-pv32pae-cpuid:	SUCCESS
Test cpuid return code: 0
[...]

Note that the specific runes to interact with xl have been placed in a
separate file, and that other backends could be easily added provided that
config files suitable for them are also added to XTF tests. A new
--toolstack option should be added then in order to tell the launcher which
toolstack to use.

Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
---
Cc: andrew.cooper3@citrix.com
Cc: Ian.Jackson@eu.citrix.com
Cc: wei.liu2@citrix.com
Cc: anshul.makkar@citrix.com
---
 Makefile                               |   2 +-
 tools/launcher/Makefile                |  12 ++++
 tools/launcher/backends/__init__.py    |   0
 tools/launcher/backends/return_code.py |  28 +++++++++
 tools/launcher/backends/xl.py          |  59 +++++++++++++++++++
 tools/launcher/xtf-launcher.py         | 101 +++++++++++++++++++++++++++++++++
 6 files changed, 201 insertions(+), 1 deletion(-)
 create mode 100644 tools/launcher/Makefile
 create mode 100644 tools/launcher/backends/__init__.py
 create mode 100644 tools/launcher/backends/return_code.py
 create mode 100644 tools/launcher/backends/xl.py
 create mode 100644 tools/launcher/xtf-launcher.py
diff mbox

Patch

diff --git a/Makefile b/Makefile
index fd8c3e0..eda9a25 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@  all:
 
 .PHONY: install
 install:
-	@for D in $(wildcard tests/*); do \
+	@for D in $(wildcard tests/* tools/*); do \
 		[ ! -e $$D/Makefile ] && continue; \
 		$(MAKE) -C $$D install; \
 	done
diff --git a/tools/launcher/Makefile b/tools/launcher/Makefile
new file mode 100644
index 0000000..2c07604
--- /dev/null
+++ b/tools/launcher/Makefile
@@ -0,0 +1,12 @@ 
+ROOT := $(abspath $(CURDIR)/../..)
+
+include $(ROOT)/build/common.mk
+
+TOOLS := xtf-launcher.py
+BACKENDS := __init__.py xl.py return_code.py
+
+install: $(TOOLS) $(addprefix backends/,$(BACKENDS))
+	@mkdir -p $(DESTDIR)/bin
+	@mkdir -p $(DESTDIR)/bin/backends
+	install -m775 -p $(TOOLS) $(DESTDIR)/bin
+	install -m664 -p $(addprefix backends/,$(BACKENDS)) $(DESTDIR)/bin/backends/
diff --git a/tools/launcher/backends/__init__.py b/tools/launcher/backends/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tools/launcher/backends/return_code.py b/tools/launcher/backends/return_code.py
new file mode 100644
index 0000000..509460a
--- /dev/null
+++ b/tools/launcher/backends/return_code.py
@@ -0,0 +1,28 @@ 
+#
+# Error codes for XTF launcher
+#
+
+# All went fine.
+SUCCESS = 0
+
+# Bug in the launcher.
+ERROR_LAUNCHER = 1
+
+# Test cannot be completed.
+ERROR_SKIP = 2
+
+# Bug in the test code or environment.
+ERROR_ERROR = 3
+
+# Bug in the code under test.
+ERROR_FAILURE = 4
+
+def code_to_str(code):
+    err_trans = {
+        0   :   'SUCCESS',
+        1   :   'LAUNCHER ERROR',
+        2   :   'SKIP',
+        3   :   'ERROR',
+        4   :   'FAILURE',
+    }
+    return err_trans[code]
diff --git a/tools/launcher/backends/xl.py b/tools/launcher/backends/xl.py
new file mode 100644
index 0000000..c1ac111
--- /dev/null
+++ b/tools/launcher/backends/xl.py
@@ -0,0 +1,59 @@ 
+#
+# XL backend for XTF
+#
+
+import subprocess, sys
+import return_code
+
+# XXX: multiprocessing is _not_ available in python < 2.6
+from multiprocessing.pool import Pool
+
+from timeit import default_timer as timer
+
+def _xl_console_helper(domain_name):
+    output = subprocess.check_output(['xl', 'console', domain_name])
+    result_line = output.splitlines()[-1]
+    if "SUCCESS" in result_line:
+        return return_code.SUCCESS
+    elif "SKIP" in result_line:
+        return return_code.ERROR_SKIP
+    elif "ERROR" in result_line:
+        return return_code.ERROR_ERROR
+    elif "FAILURE" in result_line:
+        return return_code.ERROR_FALIURE
+
+    # Something went wrong, no test report line was found (or it's invalid).
+    return return_code.ERROR_LAUNCHER
+
+class xl:
+    """XL driver for XTF"""
+
+    def __init__(self):
+        self._thpool = Pool(processes=1)
+
+    def get_supported_environments(self):
+        output = subprocess.check_output(['xl', 'info'])
+        #print output
+        for line in output.splitlines():
+            #print line
+            if not line.startswith("xen_caps"):
+                continue
+            caps = line.split()[2:]
+            return caps
+        return None
+
+    def run_test(self, test_cfg, domain_name):
+        rc = subprocess.call(['xl', 'create', '-p', test_cfg])
+        if rc:
+            sys.stderr.write("Failed to create VM from config file %s\n" %
+                             test_cfg)
+            return return_code.ERROR_LAUNCHER
+        thread = self._thpool.apply_async(_xl_console_helper, (domain_name, ))
+        start = timer()
+        rc = subprocess.call(['xl', 'unpause', domain_name])
+        if rc:
+            sys.stderr.write("Failed to unpause VM %s\n" % domain_name)
+            return return_code.ERROR_LAUNCHER
+        rc = thread.get()
+        sys.stderr.write("%s took %ds\n" % (domain_name, timer() - start))
+        return rc
diff --git a/tools/launcher/xtf-launcher.py b/tools/launcher/xtf-launcher.py
new file mode 100644
index 0000000..0685c2f
--- /dev/null
+++ b/tools/launcher/xtf-launcher.py
@@ -0,0 +1,101 @@ 
+#!/usr/bin/env python
+#
+# Launcher script for XTF
+#
+
+# XXX: JSON module is _not_ available on Python < 2.6
+import os, sys, json
+import backends.xl
+import backends.return_code as return_code
+
+from optparse import OptionParser
+
+def exit_error(error_str):
+    sys.stderr.write(error_str)
+    sys.exit(return_code.ERROR_LAUNCHER)
+
+def check_envinronment(req_envs, avail_envs):
+    env_trans = {
+        'pv32pae'   :   'xen-3.0-x86_32p',
+        'pv64'      :   'xen-3.0-x86_64',
+        'hvm32'     :   'hvm-3.0-x86_32',
+        'hvm32pse'  :   'hvm-3.0-x86_32',
+        'hvm32pae'  :   'hvm-3.0-x86_32p',
+        'hvm64'     :   'hvm-3.0-x86_64',
+    }
+    for env in req_envs:
+        if env_trans[env] not in avail_envs:
+            return False
+    return True
+
+def list():
+    for folder in os.listdir(options.folder):
+        if not os.path.isdir(os.path.join(options.folder, folder)):
+            continue
+        test_info = os.path.join(options.folder, folder, 'test-info.json')
+        if not os.path.isfile(test_info):
+            sys.stderr.write("Unable to find %s, ignoring folder\n" % testinfo)
+            continue
+        with open(test_info) as data_file:
+            info = json.load(data_file)
+        envs = backend.get_supported_environments()
+        if not envs:
+            exit_error("Unable to get Xen supported environments\n")
+        avail = check_envinronment(info["environments"], envs)
+        if not avail:
+            sys.stderr.write("Skipping test %s: not supported\n" % folder)
+            continue
+        print folder
+    return return_code.SUCCESS
+
+def test():
+    test_folder = os.path.join(options.folder, options.test)
+    if not os.path.isdir(test_folder):
+        exit_error("%s is not a directory\n" % test_folder)
+    test_info = os.path.join(test_folder, 'test-info.json')
+    if not os.path.isfile(test_info):
+        exit_error("%s doesn't exist" % test_info)
+    with open(test_info) as data_file:
+        info = json.load(data_file)
+    result = {}
+    for env in info["environments"]:
+        test_cfg = os.path.join(test_folder,
+                                "test-%s-%s.cfg" % (env, options.test))
+        test_name = "test-%s-%s" % (env, options.test)
+        if not os.path.isfile(test_cfg):
+            exit_error("Unable to find test config file %s\n" % test_cfg)
+        result[test_name] = backend.run_test(test_cfg, test_name)
+    # Print all the results
+    result_code = 0
+    for key, value in result.iteritems():
+        print "%s:\t%s" % (key, return_code.code_to_str(value))
+        # Return the highest error code in case of error.
+        result_code = max(value, result_code)
+    return result_code
+
+parser = OptionParser()
+parser.add_option("-f", "--folder", dest="folder",
+                  help="FOLDER where XTF tests are located", metavar="FOLDER")
+parser.add_option("-t", "--test", dest="test",
+                  help="NAME of test to run", metavar="NAME")
+parser.add_option("-l", "--list", action="store_true", dest="list",
+                  help="List test that are applicable to current environment")
+(options, args) = parser.parse_args()
+
+if not options.folder:
+    exit_error("folder not given\n")
+
+if options.list and options.test:
+    exit_error("options -l and -t are mutually exclusive\n")
+
+if not os.path.isdir(options.folder):
+    exit_error("%s does not exist or is not a folder\n" % options.folder)
+
+backend = backends.xl.xl()
+
+if options.list:
+    rc = list()
+elif options.test:
+    rc = test()
+
+sys.exit(rc)