Skip to content
Snippets Groups Projects
Commit 51621778 authored by Detlev Casanova's avatar Detlev Casanova
Browse files

generate-jobs.py: Add a job generator


This program does not use LQA to generate the job descriptions.

It is capable of finding all the tests for a given image and group them
into the adequate template.

Each job is written into a yaml file: job-${image_name}-${group}.yaml

Signed-off-by: default avatarDetlev Casanova <detlev.casanova@collabora.com>
parent 4dd32175
No related branches found
No related tags found
1 merge request!407Drop LQA dependency to generate jobs
#!/usr/bin/python3
###################################################################################
# Apertis LAVA Jod description generator
# Copyright (C) 2022 Collabora Ltd
# Detlev Casanova <detlev.casanova@collabora.com>
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 US
###################################################################################
import os
import argparse
import yaml
import json
import logging
import copy
import jinja2.exceptions
from jinja2 import Environment, FileSystemLoader, \
StrictUndefined, DebugUndefined
test_cases_files_dir = "test-cases"
logger = logging.getLogger('job-generator')
class DeviceNotFound(Exception):
def __init__(self, arch, board):
self.arch = arch
self.board = board
class AmbiguousDevices(Exception):
def __init__(self, arch, board, devs):
self.arch = arch
self.board = board
self.dev_names = devs
class JobGenerator:
def __init__(self, args):
self.args = args
def _get_yaml(self, file):
# load yaml file
try:
with open(file) as conf_data:
return yaml.safe_load(conf_data)
except EnvironmentError as e:
logger.error(e)
except yaml.scanner.ScannerError as e:
logger.error(e)
return None
def _find_device(self, devices_file, arch, board):
devs = self._get_yaml(devices_file)
ret = list(filter(lambda dev: devs[dev]['arch'] == arch and devs[dev]['board'] == board, devs.keys()))
if not ret:
raise DeviceNotFound(arch, board)
if len(ret) > 1:
raise AmbiguousDevices(arch, board, ret)
return devs[ret[0]]
def _get_test_metadata(self, data):
try:
tc_data = yaml.safe_load(data)
except yaml.scanner.ScannerError as e:
print("yaml format error:", e)
exit(1)
return tc_data['metadata']
def _load_tests(self):
arch = self.args.arch
osname = self.args.osname
image_type = self.args.type
tests = dict()
for filename in os.listdir(test_cases_files_dir):
if filename.endswith(('.yml', '.yaml')):
with open(os.path.join(test_cases_files_dir, filename), "r") as file:
test_meta = self._get_test_metadata(file.read())
# Make sure that this test is compatible with the current image_type and architecture
image_types = test_meta.get('image-types', {})
image_type_dict = image_types.get(image_type)
if not image_type_dict or arch not in image_type_dict:
continue
# Make sure that this test can be used on the current deployment method
image_deployment_list = [d.lower() for d in test_meta.get('image-deployment', ())]
if self.args.deployment not in image_deployment_list:
continue
# Only run automated tests
if test_meta['exec-type'] != 'automated':
continue
# Do not run sanity checks (They are included by the boot template)
if test_meta['type'] == 'sanity':
continue
group_name = test_meta.get('group')
if not group_name:
logger.warning(f"Automated test {test_meta['name']} has no group entry") # make me an error
group = tests.setdefault(group_name, []).append(test_meta['name'])
return tests
def _generate_groups_job(self, variables={}, tests={}):
# Choose the 'undefined' variable strategy
uv = (self.args.debug_vars and DebugUndefined) or StrictUndefined
# Create a template environment.
# Use list/set to remove any duplicate.
env = Environment(loader=FileSystemLoader(list(set(self.template_dirs))),
undefined=uv)
groups = list(tests.keys())
# Process job files
for group in groups:
try:
# Update variables for this group.
variables.update({"tests": tests[group]})
variables["group"] = group
# Use the default template if there isn't one for this group
job_file = "group-" + group + "-tpl.yaml"
if not os.path.isfile(os.path.join("lava", job_file)):
job_file = "group-default-tpl.yaml"
# Render data from the template
data = env.get_template(job_file).render(variables)
except TypeError as e:
logger.error("type error in group {}: {}".format(group, e))
continue
except jinja2.exceptions.TemplateNotFound as e:
logger.error("template not found: {}".format(e))
continue
except jinja2.exceptions.UndefinedError as e:
logger.error("template variable not defined in {}: {}"
.format(group, e))
continue
except KeyError as e:
logger.error("No tests found for group {}"
.format(group))
continue
filename = os.path.join(f"{self.args.output_dir}", f"job-{variables['image_name']}-{group}.yaml")
print(filename)
with open(filename, "w") as output:
output.write(data)
if self.args.verbose:
print(data)
def generate(self):
""" Generate Job descriptions
"""
default_cfg = self._get_yaml(self.args.config)
self.template_dirs = [default_cfg['template-dir']]
variables = default_cfg['variables']
# Set variables from arguments
variables['image_type'] = self.args.type
variables['image_deployment'] = self.args.deployment
variables['priority'] = self.args.priority
variables['image_date'] = self.args.date
variables['image_name'] = self.args.name
# Set variables passed with '-t'
if self.args.template_vars:
for item in self.args.template_vars:
k, v = item.split(':', 1)
variables[k] = v
# Generate metadata file for phab bridge
metadata = {
'image.version': self.args.date,
'image.release': self.args.release,
'image.type': self.args.type,
'image.arch': self.args.arch,
'image.board': self.args.board,
'image.osname': self.args.osname,
'image.flavour': self.args.deployment,
'image.name': self.args.name
}
if self.args.metadata_file:
self.args.metadata_file.write(json.dumps(metadata))
# Find our device from the <devices>.yaml file
try:
self.dev = self._find_device(self.args.device_file, self.args.arch, self.args.board)
except DeviceNotFound as e:
logger.error(f"Cannot find device: {e.arch}/{e.board}")
exit(1)
except AmbiguousDevice as e:
logger.error(f"Multiple devices for this configuration: {e.arch}/{e.board}: {e.dev_names}")
exit(1)
tests = self._load_tests()
variables.update(self.dev)
self._generate_groups_job(variables, tests)
parser = argparse.ArgumentParser(description="Generate LAVA jobs for Apertis")
parser.add_argument('--config', type=str, required=True,
help="set the config yaml file")
parser.add_argument('--arch', choices=['amd64', 'armhf', 'arm64'], required=True,
help="set the architecture of the image to be tested")
parser.add_argument('--board', choices=['uefi', 'sdk', 'uboot', 'rpi64'],
required=True,
help="set the board type of the image to be tested")
parser.add_argument('--osname', type=str, required=True,
help="set the osname of the image to be tested")
parser.add_argument('--deployment', choices=['ostree', 'apt', 'nfs', 'lxc'],
required=True,
help="set the deployment method of the image")
parser.add_argument('--type',
choices=['hmi', 'fixedfunction', 'basesdk', 'sdk'],
required=True,
help="set the type of the image to be tested")
parser.add_argument('--release', type=str, required=True,
help="set the release number of the image to be tested "
"(e.g.: v2023dev1)")
parser.add_argument('--date', type=str, required=True,
help="set the date of the image to be tested "
"(e.g.: 20230524.1337)")
parser.add_argument('--name', type=str, required=True,
help="set the name of the image to be tested "
"(e.g.: apertis-apt-v2022-fixedfunction-arm64-uboot_20220106.1337)")
parser.add_argument('--output-dir', type=str,
help="where to save the generated jobs", default=".")
parser.add_argument('--metadata-file', type=argparse.FileType("w"),
help="where to save the metadata")
parser.add_argument('--log-file', type=str, help="set the log file")
parser.add_argument('-d', '--device-file', metavar='DEVICES.yaml',
help="set device file", required=True)
parser.add_argument('-t', '--template-vars', action='append',
type=str, help="set 'field:value' "
"template variables/values "
"(can be given multiple times)")
parser.add_argument('-v', '--verbose', action='store_true',
help="Verbose mode (e.g. print the resulting "
"yaml)")
parser.add_argument('--debug-vars', action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--priority',
choices=['high', 'medium', 'low'],
default='medium',
help="Set the job priority"),
args = parser.parse_args()
try:
JobGenerator(args).generate()
except KeyboardInterrupt:
pass
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment