os-autoinst-distri-fedora/kstest-converter

286 lines
11 KiB
Plaintext
Raw Normal View History

#!/bin/python3
import argparse
import json
import logging
import os
import re
SKIPS = [
# we haven't really figured out how to do NFS in openQA yet
'nfs-repo-and-addon',
# the prep for this one probably needs to be in fedora_openqa_schedule
'liveimg',
# i'll figure this one out later
'basic-ostree',
# FIXMEs:
# keyboard - changes keyboard layout, fucks with openQA's script runner
# hostname - changes hostname, breaks 'text_console_login' needle
# packages-and-groups-1 - boots to GUI login
# encrypt-device - need to set ENCRYPT_PASSWORD, also 'kickstart insufficient'?
]
SUBSTS = [
('@KSTEST_URL@', '--mirrorlist=https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-$releasever&arch=$basearch'),
('@KSTEST_FTP_URL@', 'ftp://mirror.utexas.edu/pub/fedora/linux/development/rawhide/Everything/$basearch/os/'),
# we need to reboot not shutdown for openQA
('shutdown', 'reboot')
]
def _find_tests(dir):
"""Find the tests to run from a directory name (should be a
checkout of kickstart-tests).
"""
# find .ks.in files
ksins = [ks for ks in os.listdir(dir) if ks.endswith('ks.in')]
# filter tests we can't do yet
ksins = [ks for ks in ksins if not any(ks.startswith(skip) for skip in SKIPS)]
# strip .ks.in
return [ksin.replace('.ks.in', '') for ksin in ksins]
def prep_kickstarts(indir, outdir):
"""Produce kickstarts in 'outdir' from .ks.in files in 'indir'.
"""
if not os.path.isdir(outdir):
raise ValueError("Output directory {0} does not exist!".format(outdir))
tests = _find_tests(indir)
if not tests:
raise ValueError("No tests found!")
for test in tests:
# read in the .ks.in file
with open('{0}/{1}.ks.in'.format(indir, test), 'r') as ksinfh:
kstext = ksinfh.read()
for (orig, repl) in SUBSTS:
kstext = kstext.replace(orig, repl)
# write out the processed .ks
ksout = "{0}.ks".format(test)
with open('{0}/{1}'.format(outdir, ksout), 'w') as ksoutfh:
ksoutfh.write(kstext)
def merge_templates(indir, baseurl, tempfile, outfile):
"""Produce openQA test suites and job templates for all tests in
indir, merge them with the existing openQA templates file
'tempfile', and write the combined templates file as 'outfile'.
'baseurl' is the URL to the path where the kickstart files for
the tests can be found.
"""
tests = _find_tests(indir)
if not tests:
raise ValueError("No tests found!")
testsuites = [create_testsuite(test, indir, baseurl) for test in tests]
with open(tempfile, 'r') as tempfh:
templates = json.loads(tempfh.read())
templates = merge_testsuites(templates, testsuites)
with open(outfile, 'w') as outfh:
outfh.write(json.dumps(templates, sort_keys=True, indent=4,
separators=(',', ': ')))
def _get_settings_disk(sh):
"""Given text of a kickstart_tests test (.sh file) as sh, return
a list of appropriate openQA settings dicts for hard disks.
"""
# most prepare_disks just create empty disks of a given size in
# a standard way. in practice it's good enough to just create the
# right number of disks at openQA's standard size (10GB), that'll
# make things work. This is a rough assumption that may break in
# future. A missing openQA feature here is you can't tell it to
# create multiple clean disk images with different sizes.
settings = []
simpre = re.compile(r'qemu-img create -q -f qcow2 \$\{(tmp|disk)dir\}/disk-.\.img \d+G')
numdisks = len(simpre.findall(sh))
# the one tricky case that exists so far is the driverdisk case.
# it gets created elsewhere. here we just point to it.
if 'mkdud.py' in sh:
numdisks += 1
settings.append({'key': 'HDD_{0}'.format(str(numdisks)), 'value': "driverdisk.img"})
if numdisks > 0:
settings.append({'key': 'NUMDISKS', 'value': str(numdisks)})
return settings
def _get_settings_postinstall(sh):
"""Given text of a kickstart_tests test (.sh file) as sh, return
a list of appropriate openQA settings dict for post-install test
settings (currently a single-item list, but we're future-proofing
here).
"""
# just one test checks for RESULT in /home instead of /root
if '/home/RESULT' in sh:
return [{'key': 'POSTINSTALL', 'value': 'kstest_home'}]
else:
return [{'key': 'POSTINSTALL', 'value': 'kstest_root'}]
def _get_settings_keyboard(ksin):
"""Given text of a kickstart_tests .ks.in file as ksin, return
a list of appropriate openQA settings dict for keyboard test
settings (currently a single-item list, but we're future-proofing
here).
"""
# if the kickstart sets a keyboard layout, we set VNCKB to the
# same layout. This tells openQA to run qemu with '-k (layout)',
# which makes it use that layout for converting keysyms received
# via VNC to keycodes which are passed on to the guest OS, which
# converts them back into keysyms. Basically if we want to set a
# non-US layout in the guest and have 'type_string qwerty' still
# type 'qwerty' and not 'qwertz' or 'azert' or something, this is
# how we do that.
match = re.search(r'--vckeymap (\S+)', ksin)
if match:
return [{'key': 'VNCKB', 'value': match.group(1)}]
else:
return []
def create_testsuite(test, path, baseurl):
"""Create an openQA 'test suite' for a given kickstart_test. test
is the test name, path is the directory the test files are in.
"""
# duh. these are all kickstart tests.
settings = [{'key': 'KICKSTART', 'value': '1'}]
# get text of .sh and .ks.in files
with open('{0}/{1}.sh'.format(path, test), 'r') as shfh:
sh = shfh.read()
with open('{0}/{1}.ks.in'.format(path, test), 'r') as ksinfh:
ksin = ksinfh.read()
# call all the functions for getting different settings.
settings.extend(_get_settings_disk(sh))
settings.extend(_get_settings_postinstall(sh))
settings.extend(_get_settings_keyboard(ksin))
# do some very simple ones ourselves (to avoid this function
# growing too much, the rule is that if it needs more than one
# line, split it out).
# root password. for now we assume there is one.
settings.append({'key': 'ROOT_PASSWORD', 'value': re.search(r'rootpw (.+)', ksin).group(1)})
# kickstart URL
settings.append({'key': 'GRUB', 'value': "inst.ks={0}/{1}.ks".format(baseurl.strip('/'), test)})
# we never want to do a user login for these
settings.append({'key': 'USER_LOGIN', 'value': "false"})
return {'name': "kstest_{0}".format(test), 'settings': settings}
def merge_testsuites(templates, testsuites, machine='64bit', arch='x86_64',
distri='fedora', prio=50, flavor='kstests', version='*'):
"""Merge some test suites (as produced by create_testsuite) into
'templates', which is expected to be an openQA templates file
parsed into a dict. Returns the merged dict.
"""
for testsuite in testsuites:
templates['TestSuites'].append(testsuite)
jobt = {
'machine': {'name': machine},
'prio': prio,
'test_suite': {'name': testsuite['name']},
'product': {
'arch': arch,
'distri': distri,
'flavor': flavor,
'version': version,
},
}
templates['JobTemplates'].append(jobt)
foundprod = False
for product in templates['Products']:
if (
product['flavor'] == flavor and product['distri'] == distri and
product['version'] == version and product['arch'] == arch
):
foundprod = True
break
if not foundprod:
# add a Product for the flavor
templates['Products'].append(
{
'arch': arch,
'distri': distri,
'flavor': flavor,
'name': "",
'settings': [],
'version': version
}
)
return templates
def cmd_kickstarts(args):
"""kickstarts subcommand function: produce kickstarts from .ks.in
files.
"""
try:
prep_kickstarts(args.indir, args.outdir)
except ValueError as err:
sys.exit(err)
def cmd_templates(args):
"""templates subcommand function: produce openQA test suites and
job templates and merge into a templates file.
"""
try:
merge_templates(args.indir, args.baseurl, args.tempfile, args.outfile)
except ValueError as err:
sys.exit(err)
def parse_args():
"""Parse arguments with argparse."""
parser = argparse.ArgumentParser(description=(
"Translation layer to convert anaconda kickstart-tests into "
"openQA tests. 'kickstarts' parses kickstart-tests .ks.in "
"files to kickstarts. 'templates' produces openQA test suites "
"from kickstart-tests and merges them into a pre-existing "
"openQA templats file."))
parser.add_argument(
'-l', '--loglevel', help="The level of log messages to show",
choices=('debug', 'info', 'warning', 'error', 'critical'),
default='info')
# This is a workaround for a somewhat infamous argparse bug
# in Python 3. See:
# https://stackoverflow.com/questions/23349349/argparse-with-required-subparser
# http://bugs.python.org/issue16308
subparsers = parser.add_subparsers(dest='subcommand')
subparsers.required = True
parser_kickstarts = subparsers.add_parser(
'kickstarts', description="Produce kickstarts from .ks.in "
"files and write them to a specified directory.")
parser_kickstarts.add_argument(
'indir', help="Input directory (containing .ks.in files)")
parser_kickstarts.add_argument(
'outdir', help="Output directory (where .ks files are written")
parser_kickstarts.set_defaults(func=cmd_kickstarts)
parser_templates = subparsers.add_parser(
'templates', description="Produce openQA 'test suites' and "
"'job templates' from anaconda-kickstarts tests and merge "
"them into an existing openQA templates file.")
parser_templates.add_argument(
'indir', help="Input directory (containing .ks.in and .sh files)")
parser_templates.add_argument(
'baseurl', help="URL to a directory containing .ks files (as "
"produced by 'kickstarts' subcommand)")
parser_templates.add_argument(
'tempfile', help="Path to openQA templates file (must be JSON "
"format, not Perl)")
parser_templates.add_argument('outfile', help="Path to output file")
parser_templates.set_defaults(func=cmd_templates)
return parser.parse_args()
def main():
"""Main loop."""
try:
args = parse_args()
loglevel = getattr(
logging, args.loglevel.upper(), logging.INFO)
logging.basicConfig(level=loglevel)
args.func(args)
except KeyboardInterrupt:
sys.stderr.write("Interrupted, exiting...\n")
sys.exit(1)
if __name__ == '__main__':
main()