@@ -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
new file mode 100644
@@ -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/
new file mode 100644
new file mode 100644
@@ -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]
new file mode 100644
@@ -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
new file mode 100644
@@ -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)
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