Fathi Boudra | 9984a98 | 2019-12-17 17:37:58 +0200 | [diff] [blame^] | 1 | import argparse |
| 2 | import os |
| 3 | import re |
| 4 | import requests |
| 5 | import sys |
| 6 | import StringIO |
| 7 | from copy import deepcopy |
| 8 | from string import Template |
| 9 | from jinja2 import Environment, FileSystemLoader, StrictUndefined |
| 10 | from ruamel.yaml import YAML |
| 11 | |
| 12 | |
| 13 | try: |
| 14 | from urllib.parse import urlsplit |
| 15 | except ImportError: |
| 16 | from urlparse import urlsplit |
| 17 | |
| 18 | |
| 19 | # Templates base path |
| 20 | template_base_path = 'configs/openembedded-lkft/lava-job-definitions' |
| 21 | testplan_base_path = 'configs/openembedded-lkft/lava-job-definitions/' |
| 22 | testplan_device_path = 'devices/' |
| 23 | # Snapshots base URL |
| 24 | snapshots_url = 'https://snapshots.linaro.org/openembedded/lkft' |
| 25 | |
| 26 | def parse_template(yaml_string): |
| 27 | ''' |
| 28 | Round trip lava_job through ruamel to test parsing and |
| 29 | improve formatting. Comments are preserved. |
| 30 | |
| 31 | In: yaml-formatted string |
| 32 | Out: validated yaml-formatted string |
| 33 | ''' |
| 34 | yaml = YAML() |
| 35 | # ruamel does not provide a mechanism to dump to string, so use StringIO |
| 36 | # to catch it |
| 37 | output = StringIO.StringIO() |
| 38 | yaml.dump(yaml.load(yaml_string), output) |
| 39 | # strip empty lines from output |
| 40 | return re.sub(r'^\s*$\n', '', output.getvalue(), flags=re.MULTILINE) |
| 41 | |
| 42 | def get_job_name(lava_job_string): |
| 43 | ''' |
| 44 | In: yaml-formatted string |
| 45 | Out: LAVA job's name |
| 46 | ''' |
| 47 | yaml = YAML() |
| 48 | lava_job = yaml.load(lava_job_string) |
| 49 | return lava_job['job_name'] |
| 50 | |
| 51 | def _load_template(template_name, template_path, device_type): |
| 52 | template = '' |
| 53 | template_file_name = '' |
| 54 | |
| 55 | if template_name: |
| 56 | template_file_name = "%s/%s/%s" % (template_path, |
| 57 | device_type, |
| 58 | template_name) |
| 59 | if os.path.exists(template_file_name): |
| 60 | with open(template_file_name, 'r') as f: |
| 61 | template = f.read() |
| 62 | else: |
| 63 | print('template (%s) was specified but not exists' % |
| 64 | template_file_name) |
| 65 | sys.exit(1) |
| 66 | |
| 67 | return template, template_file_name |
| 68 | |
| 69 | |
| 70 | def _submit_to_squad(lava_job, lava_url_base, qa_server_api, qa_server_base, qa_token, quiet): |
| 71 | headers = { |
| 72 | "Auth-Token": qa_token |
| 73 | } |
| 74 | |
| 75 | try: |
| 76 | data = { |
| 77 | "definition": lava_job, |
| 78 | "backend": urlsplit(lava_url_base).netloc # qa-reports backends are named as lava instances |
| 79 | } |
| 80 | print("Submit to: %s" % qa_server_api) |
| 81 | results = requests.post(qa_server_api, data=data, headers=headers, |
| 82 | timeout=31) |
| 83 | if results.status_code < 300: |
| 84 | print("%s/testjob/%s %s" % (qa_server_base, results.text, get_job_name(lava_job))) |
| 85 | else: |
| 86 | print(results.status_code) |
| 87 | print(results.text) |
| 88 | except requests.exceptions.RequestException as err: |
| 89 | print("QA Reports submission failed") |
| 90 | if not quiet: |
| 91 | print("offending job definition:") |
| 92 | print(lava_job) |
| 93 | |
| 94 | |
| 95 | def main(): |
| 96 | parser = argparse.ArgumentParser() |
| 97 | parser.add_argument("--device-type", |
| 98 | help="Device type in LAVA", |
| 99 | dest="device_type", |
| 100 | required=True) |
| 101 | parser.add_argument("--environment", |
| 102 | help="User specified the environment name, prefix or suffix won't be used", |
| 103 | dest="environment", |
| 104 | default="") |
| 105 | parser.add_argument("--env-prefix", |
| 106 | help="Prefix for the environment name", |
| 107 | dest="env_prefix", |
| 108 | default="") |
| 109 | parser.add_argument("--env-suffix", |
| 110 | help="Suffix for the environment name", |
| 111 | dest="env_suffix", |
| 112 | default="") |
| 113 | parser.add_argument("--build-number", |
| 114 | help="Build number", |
| 115 | dest="build_number", |
| 116 | required=True) |
| 117 | parser.add_argument("--qa-server-team", |
| 118 | help="Team in QA Reports service", |
| 119 | dest="qa_server_team", |
| 120 | required=True) |
| 121 | parser.add_argument("--qa-server-project", |
| 122 | help="Project in QA Reports service", |
| 123 | dest="qa_server_project", |
| 124 | required=True) |
| 125 | parser.add_argument("--qa-server", |
| 126 | help="QA Reports server", |
| 127 | dest="qa_server", |
| 128 | default="https://qa-reports.linaro.org") |
| 129 | parser.add_argument("--qa-token", |
| 130 | help="QA Reports token", |
| 131 | dest="qa_token", |
| 132 | default=os.environ.get('QA_REPORTS_TOKEN')) |
| 133 | parser.add_argument("--lava-server", |
| 134 | help="LAVA server URL", |
| 135 | dest="lava_server", |
| 136 | required=True) |
| 137 | parser.add_argument("--git-commit", |
| 138 | help="git commit ID", |
| 139 | dest="git_commit", |
| 140 | required=True) |
| 141 | parser.add_argument("--template-path", |
| 142 | help="Path to LAVA job templates", |
| 143 | dest="template_path", |
| 144 | default=template_base_path) |
| 145 | parser.add_argument("--testplan-path", |
| 146 | help="Path to Jinja2 LAVA job templates", |
| 147 | dest="testplan_path", |
| 148 | default=testplan_base_path) |
| 149 | parser.add_argument("--testplan-device-path", |
| 150 | help="Relative path to Jinja2 device deployment fragments", |
| 151 | dest="testplan_device_path", |
| 152 | default=testplan_device_path) |
| 153 | parser.add_argument("--template-base-pre", |
| 154 | help="base template used to construct templates, previous", |
| 155 | dest="template_base_pre") |
| 156 | parser.add_argument("--template-base-post", |
| 157 | help="base template used to construct templates, posterior", |
| 158 | dest="template_base_post") |
| 159 | parser.add_argument("--template-names", |
| 160 | help="list of the templates to submit for testing", |
| 161 | dest="template_names", |
| 162 | nargs="+", |
| 163 | default=[]) |
| 164 | parser.add_argument("--test-plan", |
| 165 | help="""list of the Jinja2 templates to submit for testing. |
| 166 | It is assumed that the templates produce valid LAVA job |
| 167 | definitions. All varaibles are substituted using Jinja2 |
| 168 | engine. This includes environment variables.""", |
| 169 | dest="test_plan", |
| 170 | nargs="+", |
| 171 | default=[]) |
| 172 | parser.add_argument("--dry-run", |
| 173 | help="""Prepare and write templates to tmp/. |
| 174 | Don't submit to actual servers.""", |
| 175 | action='store_true', |
| 176 | dest="dryrun") |
| 177 | parser.add_argument("--quiet", |
| 178 | help="Only output the final qa-reports URL", |
| 179 | action='store_true', |
| 180 | dest="quiet") |
| 181 | |
| 182 | args, _ = parser.parse_known_args() |
| 183 | |
| 184 | output_path = "tmp" |
| 185 | if args.dryrun: |
| 186 | if not os.path.exists(output_path): |
| 187 | os.mkdir(output_path) |
| 188 | if args.qa_token is None and not args.dryrun: |
| 189 | print("QA_REPORTS_TOKEN is missing") |
| 190 | sys.exit(1) |
| 191 | |
| 192 | qa_server_base = args.qa_server |
| 193 | if not (qa_server_base.startswith("http://") or qa_server_base.startswith("https://")): |
| 194 | qa_server_base = "https://" + qa_server_base |
| 195 | qa_server_team = args.qa_server_team |
| 196 | qa_server_project = args.qa_server_project |
| 197 | qa_server_build = args.git_commit |
| 198 | |
| 199 | if not args.environment: |
| 200 | # when user not specify value for the environment option, |
| 201 | # use the device_type as before |
| 202 | qa_server_env = args.env_prefix + args.device_type + args.env_suffix |
| 203 | else: |
| 204 | # when user specified value for the environment option, |
| 205 | # use the user specified value |
| 206 | qa_server_env = args.environment |
| 207 | |
| 208 | qa_server_api = "%s/api/submitjob/%s/%s/%s/%s" % ( |
| 209 | qa_server_base, |
| 210 | qa_server_team, |
| 211 | qa_server_project, |
| 212 | qa_server_build, |
| 213 | qa_server_env) |
| 214 | lava_server = args.lava_server |
| 215 | if not (lava_server.startswith("http://") or lava_server.startswith("https://")): |
| 216 | lava_server = "https://" + lava_server |
| 217 | lava_url_base = "%s://%s/" % (urlsplit(lava_server).scheme, urlsplit(lava_server).netloc) |
| 218 | |
| 219 | template_base_pre, _ = _load_template(args.template_base_pre, |
| 220 | args.template_path, |
| 221 | args.device_type) |
| 222 | template_base_post, _ = _load_template(args.template_base_post, |
| 223 | args.template_path, |
| 224 | args.device_type) |
| 225 | lava_jobs = [] |
| 226 | for test in args.template_names: |
| 227 | test_template, template_file_name = _load_template(test, |
| 228 | args.template_path, |
| 229 | args.device_type) |
| 230 | if template_base_pre: |
| 231 | test_template = "%s\n%s" % (template_base_pre, test_template) |
| 232 | if template_base_post: |
| 233 | test_template = "%s\n%s" % (test_template, template_base_post) |
| 234 | |
| 235 | template = Template(test_template) |
| 236 | print("using template: %s" % template_file_name) |
| 237 | lava_job = template.substitute(os.environ) |
| 238 | lava_job = parse_template(lava_job) |
| 239 | lava_jobs.append(lava_job) |
| 240 | |
| 241 | if not args.quiet: |
| 242 | print(lava_job) |
| 243 | if args.dryrun: |
| 244 | testpath = os.path.join(output_path, args.device_type, test) |
| 245 | if not os.path.exists(os.path.dirname(testpath)): |
| 246 | os.makedirs(os.path.dirname(testpath)) |
| 247 | with open(os.path.join(testpath), 'w') as f: |
| 248 | f.write(lava_job) |
| 249 | |
| 250 | THIS_DIR = os.path.abspath(args.testplan_path) |
| 251 | # prevent creating templates when variables are missing |
| 252 | j2_env = Environment(loader=FileSystemLoader(THIS_DIR, followlinks=True), undefined=StrictUndefined) |
| 253 | context = deepcopy(os.environ) |
| 254 | context.update({"device_type": os.path.join(args.testplan_device_path, args.device_type)}) |
| 255 | for test in args.test_plan: |
| 256 | ''' Prepare lava jobs ''' |
| 257 | lava_job = j2_env.get_template(test).render(context) |
| 258 | lava_job = parse_template(lava_job) |
| 259 | lava_jobs.append(lava_job) |
| 260 | |
| 261 | if not args.quiet: |
| 262 | print(lava_job) |
| 263 | if args.dryrun: |
| 264 | testpath = os.path.join(output_path, args.device_type, test) |
| 265 | if not os.path.exists(os.path.dirname(testpath)): |
| 266 | os.makedirs(os.path.dirname(testpath)) |
| 267 | with open(os.path.join(testpath), 'w') as f: |
| 268 | f.write(lava_job) |
| 269 | |
| 270 | for lava_job in lava_jobs: |
| 271 | ''' Submit lava jobs ''' |
| 272 | if not args.dryrun: |
| 273 | _submit_to_squad(lava_job, |
| 274 | lava_url_base, |
| 275 | qa_server_api, |
| 276 | qa_server_base, |
| 277 | args.qa_token, |
| 278 | args.quiet) |
| 279 | |
| 280 | |
| 281 | if __name__ == "__main__": |
| 282 | main() |