post-build-lava: initial helper job to submit lava job

Signed-off-by: Fathi Boudra <fathi.boudra@linaro.org>
Change-Id: I0e487cf29a5064fffe68069d2c3a74b307c0cd65
diff --git a/post-build-lava/submit_for_testing.py b/post-build-lava/submit_for_testing.py
new file mode 100644
index 0000000..f423673
--- /dev/null
+++ b/post-build-lava/submit_for_testing.py
@@ -0,0 +1,282 @@
+import argparse
+import os
+import re
+import requests
+import sys
+import StringIO
+from copy import deepcopy
+from string import Template
+from jinja2 import Environment, FileSystemLoader, StrictUndefined
+from ruamel.yaml import YAML
+
+
+try:
+    from urllib.parse import urlsplit
+except ImportError:
+    from urlparse import urlsplit
+
+
+# Templates base path
+template_base_path = 'configs/openembedded-lkft/lava-job-definitions'
+testplan_base_path = 'configs/openembedded-lkft/lava-job-definitions/'
+testplan_device_path = 'devices/'
+# Snapshots base URL
+snapshots_url = 'https://snapshots.linaro.org/openembedded/lkft'
+
+def parse_template(yaml_string):
+    '''
+        Round trip lava_job through ruamel to test parsing and
+        improve formatting. Comments are preserved.
+
+        In: yaml-formatted string
+        Out: validated yaml-formatted string
+    '''
+    yaml = YAML()
+    # ruamel does not provide a mechanism to dump to string, so use StringIO
+    # to catch it
+    output = StringIO.StringIO()
+    yaml.dump(yaml.load(yaml_string), output)
+    # strip empty lines from output
+    return re.sub(r'^\s*$\n', '', output.getvalue(), flags=re.MULTILINE)
+
+def get_job_name(lava_job_string):
+    '''
+        In: yaml-formatted string
+        Out: LAVA job's name
+    '''
+    yaml = YAML()
+    lava_job = yaml.load(lava_job_string)
+    return lava_job['job_name']
+
+def _load_template(template_name, template_path, device_type):
+    template = ''
+    template_file_name = ''
+
+    if template_name:
+        template_file_name = "%s/%s/%s" % (template_path,
+                                           device_type,
+                                           template_name)
+        if os.path.exists(template_file_name):
+            with open(template_file_name, 'r') as f:
+                template = f.read()
+        else:
+            print('template (%s) was specified but not exists' %
+                  template_file_name)
+            sys.exit(1)
+
+    return template, template_file_name
+
+
+def _submit_to_squad(lava_job, lava_url_base, qa_server_api, qa_server_base, qa_token, quiet):
+    headers = {
+        "Auth-Token": qa_token
+    }
+
+    try:
+        data = {
+            "definition": lava_job,
+            "backend": urlsplit(lava_url_base).netloc  # qa-reports backends are named as lava instances
+        }
+        print("Submit to: %s" % qa_server_api)
+        results = requests.post(qa_server_api, data=data, headers=headers,
+                                timeout=31)
+        if results.status_code < 300:
+            print("%s/testjob/%s %s" % (qa_server_base, results.text, get_job_name(lava_job)))
+        else:
+            print(results.status_code)
+            print(results.text)
+    except requests.exceptions.RequestException as err:
+        print("QA Reports submission failed")
+        if not quiet:
+            print("offending job definition:")
+            print(lava_job)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--device-type",
+                        help="Device type in LAVA",
+                        dest="device_type",
+                        required=True)
+    parser.add_argument("--environment",
+                        help="User specified the environment name, prefix or suffix won't be used",
+                        dest="environment",
+                        default="")
+    parser.add_argument("--env-prefix",
+                        help="Prefix for the environment name",
+                        dest="env_prefix",
+                        default="")
+    parser.add_argument("--env-suffix",
+                        help="Suffix for the environment name",
+                        dest="env_suffix",
+                        default="")
+    parser.add_argument("--build-number",
+                        help="Build number",
+                        dest="build_number",
+                        required=True)
+    parser.add_argument("--qa-server-team",
+                        help="Team in QA Reports service",
+                        dest="qa_server_team",
+                        required=True)
+    parser.add_argument("--qa-server-project",
+                        help="Project in QA Reports service",
+                        dest="qa_server_project",
+                        required=True)
+    parser.add_argument("--qa-server",
+                        help="QA Reports server",
+                        dest="qa_server",
+                        default="https://qa-reports.linaro.org")
+    parser.add_argument("--qa-token",
+                        help="QA Reports token",
+                        dest="qa_token",
+                        default=os.environ.get('QA_REPORTS_TOKEN'))
+    parser.add_argument("--lava-server",
+                        help="LAVA server URL",
+                        dest="lava_server",
+                        required=True)
+    parser.add_argument("--git-commit",
+                        help="git commit ID",
+                        dest="git_commit",
+                        required=True)
+    parser.add_argument("--template-path",
+                        help="Path to LAVA job templates",
+                        dest="template_path",
+                        default=template_base_path)
+    parser.add_argument("--testplan-path",
+                        help="Path to Jinja2 LAVA job templates",
+                        dest="testplan_path",
+                        default=testplan_base_path)
+    parser.add_argument("--testplan-device-path",
+                        help="Relative path to Jinja2 device deployment fragments",
+                        dest="testplan_device_path",
+                        default=testplan_device_path)
+    parser.add_argument("--template-base-pre",
+                        help="base template used to construct templates, previous",
+                        dest="template_base_pre")
+    parser.add_argument("--template-base-post",
+                        help="base template used to construct templates, posterior",
+                        dest="template_base_post")
+    parser.add_argument("--template-names",
+                        help="list of the templates to submit for testing",
+                        dest="template_names",
+                        nargs="+",
+                        default=[])
+    parser.add_argument("--test-plan",
+                        help="""list of the Jinja2 templates to submit for testing.
+                        It is assumed that the templates produce valid LAVA job
+                        definitions. All varaibles are substituted using Jinja2
+                        engine. This includes environment variables.""",
+                        dest="test_plan",
+                        nargs="+",
+                        default=[])
+    parser.add_argument("--dry-run",
+                        help="""Prepare and write templates to tmp/.
+                        Don't submit to actual servers.""",
+                        action='store_true',
+                        dest="dryrun")
+    parser.add_argument("--quiet",
+                        help="Only output the final qa-reports URL",
+                        action='store_true',
+                        dest="quiet")
+
+    args, _ = parser.parse_known_args()
+
+    output_path = "tmp"
+    if args.dryrun:
+        if not os.path.exists(output_path):
+            os.mkdir(output_path)
+    if args.qa_token is None and not args.dryrun:
+        print("QA_REPORTS_TOKEN is missing")
+        sys.exit(1)
+
+    qa_server_base = args.qa_server
+    if not (qa_server_base.startswith("http://") or qa_server_base.startswith("https://")):
+        qa_server_base = "https://" + qa_server_base
+    qa_server_team = args.qa_server_team
+    qa_server_project = args.qa_server_project
+    qa_server_build = args.git_commit
+
+    if not args.environment:
+        # when user not specify value for the environment option,
+        # use the device_type as before
+        qa_server_env = args.env_prefix + args.device_type + args.env_suffix
+    else:
+        # when user specified value for the environment option,
+        # use the user specified value
+        qa_server_env = args.environment
+
+    qa_server_api = "%s/api/submitjob/%s/%s/%s/%s" % (
+        qa_server_base,
+        qa_server_team,
+        qa_server_project,
+        qa_server_build,
+        qa_server_env)
+    lava_server = args.lava_server
+    if not (lava_server.startswith("http://") or lava_server.startswith("https://")):
+        lava_server = "https://" + lava_server
+    lava_url_base = "%s://%s/" % (urlsplit(lava_server).scheme, urlsplit(lava_server).netloc)
+
+    template_base_pre, _ = _load_template(args.template_base_pre,
+                                          args.template_path,
+                                          args.device_type)
+    template_base_post, _ = _load_template(args.template_base_post,
+                                           args.template_path,
+                                           args.device_type)
+    lava_jobs = []
+    for test in args.template_names:
+        test_template, template_file_name = _load_template(test,
+                                                           args.template_path,
+                                                           args.device_type)
+        if template_base_pre:
+            test_template = "%s\n%s" % (template_base_pre, test_template)
+        if template_base_post:
+            test_template = "%s\n%s" % (test_template, template_base_post)
+
+        template = Template(test_template)
+        print("using template: %s" % template_file_name)
+        lava_job = template.substitute(os.environ)
+        lava_job = parse_template(lava_job)
+        lava_jobs.append(lava_job)
+
+        if not args.quiet:
+            print(lava_job)
+        if args.dryrun:
+            testpath = os.path.join(output_path, args.device_type, test)
+            if not os.path.exists(os.path.dirname(testpath)):
+                os.makedirs(os.path.dirname(testpath))
+            with open(os.path.join(testpath), 'w') as f:
+                f.write(lava_job)
+
+    THIS_DIR = os.path.abspath(args.testplan_path)
+    # prevent creating templates when variables are missing
+    j2_env = Environment(loader=FileSystemLoader(THIS_DIR, followlinks=True), undefined=StrictUndefined)
+    context = deepcopy(os.environ)
+    context.update({"device_type": os.path.join(args.testplan_device_path, args.device_type)})
+    for test in args.test_plan:
+        ''' Prepare lava jobs '''
+        lava_job = j2_env.get_template(test).render(context)
+        lava_job = parse_template(lava_job)
+        lava_jobs.append(lava_job)
+
+        if not args.quiet:
+            print(lava_job)
+        if args.dryrun:
+            testpath = os.path.join(output_path, args.device_type, test)
+            if not os.path.exists(os.path.dirname(testpath)):
+                os.makedirs(os.path.dirname(testpath))
+            with open(os.path.join(testpath), 'w') as f:
+                f.write(lava_job)
+
+    for lava_job in lava_jobs:
+        ''' Submit lava jobs '''
+        if not args.dryrun:
+            _submit_to_squad(lava_job,
+                lava_url_base,
+                qa_server_api,
+                qa_server_base,
+                args.qa_token,
+                args.quiet)
+
+
+if __name__ == "__main__":
+    main()