1
0
mirror of https://pagure.io/fedora-qa/os-autoinst-distri-fedora.git synced 2025-08-22 07:15:44 +00:00

Add ProfileGroups

This is another new convenience feature. Groups of profiles let
us avoid repeating commonly-used sets. A test suite can specify
a group of profiles, with a base priority. The priority value
for each profile within the ProfileGroup is added to the base
priority in the test suite.

Signed-off-by: Adam Williamson <awilliam@redhat.com>
This commit is contained in:
Adam Williamson 2025-05-13 18:22:14 -07:00
parent 046d9df432
commit bdf74e74b4
10 changed files with 107 additions and 29 deletions

View File

@ -136,6 +136,7 @@ def merge_inputs(inputs, validate=False, clean=False):
flavors = {} flavors = {}
products = {} products = {}
profiles = {} profiles = {}
pgroups = {}
testsuites = {} testsuites = {}
jobtemplates = [] jobtemplates = []
@ -165,6 +166,7 @@ def merge_inputs(inputs, validate=False, clean=False):
('Flavors', flavors), ('Flavors', flavors),
('Products', products), ('Products', products),
('Profiles', profiles), ('Profiles', profiles),
('ProfileGroups', pgroups),
('JobTemplates', jobtemplates), ('JobTemplates', jobtemplates),
): ):
if datatype in data: if datatype in data:
@ -177,14 +179,22 @@ def merge_inputs(inputs, validate=False, clean=False):
for (name, newsuite) in data['TestSuites'].items(): for (name, newsuite) in data['TestSuites'].items():
try: try:
existing = testsuites[name] existing = testsuites[name]
# combine and stash the profiles # combine and stash the profiles and groups
combinedprofiles = {}
if 'profiles' in existing:
existing['profiles'].update(newsuite['profiles']) existing['profiles'].update(newsuite['profiles'])
combinedprofiles = existing['profiles'] combinedprofiles = existing['profiles']
combinedpgroups = {}
if 'profile_groups' in existing:
existing['profile_groups'].update(newsuite.get('profile_groups', {}))
combinedpgroups = existing['profile_groups']
# now update the existing suite with the new one, this # now update the existing suite with the new one, this
# will overwrite the profiles # will overwrite the profiles and groups
existing.update(newsuite) existing.update(newsuite)
# now restore the combined profiles # now restore the combined profiles and groups
existing['profiles'] = combinedprofiles existing['profiles'] = combinedprofiles
if combinedpgroups:
existing['profile_groups'] = combinedpgroups
except KeyError: except KeyError:
testsuites[name] = newsuite testsuites[name] = newsuite
@ -201,6 +211,8 @@ def merge_inputs(inputs, validate=False, clean=False):
merged['Products'] = products merged['Products'] = products
if profiles: if profiles:
merged['Profiles'] = profiles merged['Profiles'] = profiles
if pgroups:
merged['ProfileGroups'] = pgroups
if testsuites: if testsuites:
merged['TestSuites'] = testsuites merged['TestSuites'] = testsuites
if jobtemplates: if jobtemplates:
@ -211,19 +223,26 @@ def merge_inputs(inputs, validate=False, clean=False):
schema_validate(merged, fif=True, state=state) schema_validate(merged, fif=True, state=state)
print("Input template data is valid") print("Input template data is valid")
return (machines, flavors, products, profiles, testsuites, jobtemplates) return (machines, flavors, products, profiles, pgroups, testsuites, jobtemplates)
def generate_job_templates(products, profiles, testsuites): def generate_job_templates(products, profiles, pgroups, testsuites):
"""Given machines, products, profiles and testsuites (after """Given machines, products, profiles and testsuites (after
merging and handling of flavors, but still in intermediate format), merging and handling of flavors, but still in intermediate format),
generates job templates and returns them as a list. generates job templates and returns them as a list.
""" """
jobtemplates = [] jobtemplates = []
for (name, suite) in testsuites.items(): for (name, suite) in testsuites.items():
if 'profiles' not in suite: suiteprofs = {}
for (pgroup, baseprio) in suite.get('profile_groups', {}).items():
if pgroup not in pgroups:
sys.exit(f"Error: profile group {pgroup} does not exist!")
for (gotprof, pprio) in pgroups[pgroup].items():
suiteprofs[gotprof] = pprio+baseprio
suiteprofs.update(suite.get('profiles', {}))
if not suiteprofs:
print("Warning: no profiles for test suite {}".format(name)) print("Warning: no profiles for test suite {}".format(name))
continue continue
for (profile, prio) in suite['profiles'].items(): for (profile, prio) in suiteprofs.items():
jobtemplate = {'test_suite_name': name, 'prio': prio} jobtemplate = {'test_suite_name': name, 'prio': prio}
# x86_64 compose # x86_64 compose
jobtemplate['group_name'] = 'fedora' jobtemplate['group_name'] = 'fedora'
@ -285,11 +304,13 @@ def reverse_qol(machines, flavors, products, testsuites):
temp.update(product.get("settings", {})) temp.update(product.get("settings", {}))
product["settings"] = temp product["settings"] = temp
# drop profiles from test suites - these are only used for job # drop profiles and groups from test suites - these are only used
# template generation and should not be in final output. if suite # for job template generation and should not be in final output.
# *only* contained profiles, drop it # if suite *only* contained profiles/groups, drop it
for suite in testsuites.values(): for suite in testsuites.values():
del suite['profiles'] for prop in ('profiles', 'profile_groups'):
if prop in suite:
del suite[prop]
testsuites = {name: suite for (name, suite) in testsuites.items() if suite} testsuites = {name: suite for (name, suite) in testsuites.items() if suite}
machines = to_list_of_dicts(machines) machines = to_list_of_dicts(machines)
@ -343,9 +364,9 @@ def run(args):
args = parse_args(args) args = parse_args(args)
if not args.validate and not args.write and not args.load: if not args.validate and not args.write and not args.load:
sys.exit("--no-validate specified and neither --write nor --load specified! Doing nothing.") sys.exit("--no-validate specified and neither --write nor --load specified! Doing nothing.")
(machines, flavors, products, profiles, testsuites, jobtemplates) = merge_inputs( (machines, flavors, products, profiles, pgroups, testsuites, jobtemplates) = merge_inputs(
args.files, validate=args.validate, clean=args.clean) args.files, validate=args.validate, clean=args.clean)
jobtemplates.extend(generate_job_templates(products, profiles, testsuites)) jobtemplates.extend(generate_job_templates(products, profiles, pgroups, testsuites))
(machines, products, testsuites) = reverse_qol(machines, flavors, products, testsuites) (machines, products, testsuites) = reverse_qol(machines, flavors, products, testsuites)
# now produce the output in upstream-compatible format # now produce the output in upstream-compatible format
out = {} out = {}

View File

@ -14,6 +14,7 @@
"Flavors": { "$ref": "fif-flavors.json" }, "Flavors": { "$ref": "fif-flavors.json" },
"ProductDefaults": { "$ref": "fif-productdefaults.json" }, "ProductDefaults": { "$ref": "fif-productdefaults.json" },
"Products": { "$ref": "fif-products.json" }, "Products": { "$ref": "fif-products.json" },
"ProfileGroups": { "$ref": "fif-profilegroups.json" },
"Profiles": { "$ref": "fif-profiles.json" }, "Profiles": { "$ref": "fif-profiles.json" },
"TestSuites": { "$ref": "fif-testsuites.json" }, "TestSuites": { "$ref": "fif-testsuites.json" },
"JobTemplates": { "$ref": "openqa-jobtemplates.json" } "JobTemplates": { "$ref": "openqa-jobtemplates.json" }

View File

@ -14,6 +14,7 @@
"Flavors": { "$ref": "fif-flavors.json" }, "Flavors": { "$ref": "fif-flavors.json" },
"ProductDefaults": { "$ref": "fif-productdefaults.json" }, "ProductDefaults": { "$ref": "fif-productdefaults.json" },
"Products": { "$ref": "fif-products.json" }, "Products": { "$ref": "fif-products.json" },
"ProfileGroups": { "$ref": "fif-profilegroups.json" },
"Profiles": { "$ref": "fif-profiles.json" }, "Profiles": { "$ref": "fif-profiles.json" },
"TestSuites": { "$ref": "fif-testsuites.json" }, "TestSuites": { "$ref": "fif-testsuites.json" },
"JobTemplates": { "$ref": "openqa-jobtemplates.json" } "JobTemplates": { "$ref": "openqa-jobtemplates.json" }

View File

@ -14,6 +14,7 @@
"Flavors": { "$ref": "fif-flavors.json" }, "Flavors": { "$ref": "fif-flavors.json" },
"ProductDefaults": { "$ref": "fif-productdefaults.json" }, "ProductDefaults": { "$ref": "fif-productdefaults.json" },
"Products": { "$ref": "fif-products-predefault.json" }, "Products": { "$ref": "fif-products-predefault.json" },
"ProfileGroups": { "$ref": "fif-profilegroups.json" },
"Profiles": { "$ref": "fif-profiles.json" }, "Profiles": { "$ref": "fif-profiles.json" },
"TestSuites": { "$ref": "fif-testsuites.json" }, "TestSuites": { "$ref": "fif-testsuites.json" },
"JobTemplates": { "$ref": "openqa-jobtemplates.json" } "JobTemplates": { "$ref": "openqa-jobtemplates.json" }

View File

@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "fif-profilegroup.json",
"title": "FIF single profile group schema",
"type": "object",
"minProperties": 1,
"additionalProperties": { "type": "number" }
}

View File

@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "fif-profilegroups.json",
"title": "FIF ProfileGroups object schema",
"type": "object",
"minProperties": 1,
"additionalProperties": { "$ref": "fif-profilegroup.json" }
}

View File

@ -2,8 +2,9 @@
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"$id": "fif-testsuite.json", "$id": "fif-testsuite.json",
"title": "FIF single test suite schema", "title": "FIF single test suite schema",
"required": [ "anyOf": [
"profiles" {"required": ["profiles"]},
{"required": ["profile_groups"]}
], ],
"properties": { "properties": {
"profiles": { "profiles": {
@ -11,6 +12,11 @@
"title": "A testsuite profile entry schema", "title": "A testsuite profile entry schema",
"additionalProperties": { "type": "number" } "additionalProperties": { "type": "number" }
}, },
"profile_groups": {
"type": "object",
"title": "A profile group entry schema",
"additionalProperties": { "type": "number" }
},
"settings": { "$ref": "fif-settingshash.json" } "settings": { "$ref": "fif-settingshash.json" }
}, },
"additionalProperties": false "additionalProperties": false

View File

@ -25,6 +25,11 @@
"product": "fedora-updates-server-x86_64-*" "product": "fedora-updates-server-x86_64-*"
} }
}, },
"ProfileGroups": {
"fedora-updates-server-1arch": {
"fedora-updates-server-ppc64le-*-ppc64le": 1
}
},
"TestSuites": { "TestSuites": {
"advisory_boot": { "advisory_boot": {
"profiles": { "profiles": {
@ -39,8 +44,10 @@
} }
}, },
"base_selinux": { "base_selinux": {
"profile_groups": {
"fedora-updates-server-1arch": 40
},
"profiles": { "profiles": {
"fedora-updates-server-ppc64le-*-ppc64le": 40,
"fedora-updates-server-x86_64-*-64bit": 40 "fedora-updates-server-x86_64-*-64bit": 40
} }
} }

View File

@ -65,10 +65,21 @@
"product": "fedora-Server-dvd-iso-x86_64-*" "product": "fedora-Server-dvd-iso-x86_64-*"
} }
}, },
"ProfileGroups": {
"fedora-server-2arch": {
"fedora-Server-dvd-iso-ppc64le-*-ppc64le": 0,
"fedora-Server-dvd-iso-x86_64-*-64bit": 1
},
"fedora-server-1arch": {
"fedora-Server-dvd-iso-ppc64le-*-ppc64le": 0
}
},
"TestSuites": { "TestSuites": {
"base_selinux": { "base_selinux": {
"profile_groups": {
"fedora-server-1arch": 40
},
"profiles": { "profiles": {
"fedora-Server-dvd-iso-ppc64le-*-ppc64le": 40,
"fedora-Server-dvd-iso-x86_64-*-64bit": 40 "fedora-Server-dvd-iso-x86_64-*-64bit": 40
}, },
"settings": { "settings": {
@ -81,9 +92,8 @@
} }
}, },
"install_default_upload": { "install_default_upload": {
"profiles": { "profile_groups": {
"fedora-Server-dvd-iso-ppc64le-*-ppc64le": 10, "fedora-server-2arch": 10
"fedora-Server-dvd-iso-x86_64-*-64bit": 10
}, },
"settings": { "settings": {
"PACKAGE_SET": "default", "PACKAGE_SET": "default",

View File

@ -73,20 +73,26 @@ def test_schema_validate():
) )
def test_merge_inputs(input1, input2): def test_merge_inputs(input1, input2):
"""Test for merge_inputs.""" """Test for merge_inputs."""
(machines, flavors, products, profiles, testsuites, jobtemplates) = _get_merged(input1, input2) (machines, flavors, products, profiles, pgroups, testsuites, jobtemplates) = _get_merged(
input1,
input2
)
# a few known attributes of the test data to ensure the merge worked # a few known attributes of the test data to ensure the merge worked
assert len(machines) == 2 assert len(machines) == 2
assert len(flavors) == 1 assert len(flavors) == 1
assert len(products) == 4 assert len(products) == 4
assert len(profiles) == 4 assert len(profiles) == 4
assert len(pgroups) == 3
assert not jobtemplates assert not jobtemplates
# testsuite merging is the most complex feature # testsuite merging is the most complex feature
# len should be 3 as there is 1 unique suite in each input file, # len should be 3 as there is 1 unique suite in each input file,
# and one defined in both which should be merged # and one defined in both which should be merged
assert len(testsuites) == 3 assert len(testsuites) == 3
# check the merged suite was merged correctly # check the merged suite was merged correctly
# we should have the profiles from *both* input files... # we should have the profiles and profile groups from *both*
assert len(testsuites['base_selinux']['profiles']) == 4 # input files...
assert len(testsuites['base_selinux']['profiles']) == 2
assert len(testsuites['base_selinux']['profile_groups']) == 2
# and we should still have the settings (note, combining settings # and we should still have the settings (note, combining settings
# is not supported, the last-read settings dict is always used) # is not supported, the last-read settings dict is always used)
assert len(testsuites['base_selinux']['settings']) == 6 assert len(testsuites['base_selinux']['settings']) == 6
@ -98,8 +104,8 @@ def test_merge_inputs(input1, input2):
def test_generate_job_templates(): def test_generate_job_templates():
"""Test for generate_job_templates.""" """Test for generate_job_templates."""
(machines, _, products, profiles, testsuites, _) = _get_merged() (machines, _, products, profiles, pgroups, testsuites, _) = _get_merged()
templates = fifloader.generate_job_templates(products, profiles, testsuites) templates = fifloader.generate_job_templates(products, profiles, pgroups, testsuites)
# we should get one template per profile in merged input # we should get one template per profile in merged input
assert len(templates) == 8 assert len(templates) == 8
for template in templates: for template in templates:
@ -112,9 +118,17 @@ def test_generate_job_templates():
assert item in template assert item in template
assert template['test_suite_name'] in list(testsuites.keys()) assert template['test_suite_name'] in list(testsuites.keys())
# check profile group expansion
idus = [t for t in templates if t['test_suite_name'] == 'install_default_upload']
assert len(idus) == 2
assert {t['machine_name'] for t in idus} == {'ppc64le', '64bit'}
aboots = [t for t in templates if t['test_suite_name'] == 'base_selinux']
assert len(aboots) == 4
assert {t['machine_name'] for t in aboots} == {'ppc64le', '64bit'}
def test_reverse_qol(): def test_reverse_qol():
"""Test for reverse_qol.""" """Test for reverse_qol."""
(machines, flavors, products, _, testsuites, _) = _get_merged() (machines, flavors, products, _, _, testsuites, _) = _get_merged()
(machines, products, testsuites) = fifloader.reverse_qol(machines, flavors, products, testsuites) (machines, products, testsuites) = fifloader.reverse_qol(machines, flavors, products, testsuites)
assert isinstance(machines, list) assert isinstance(machines, list)
assert isinstance(products, list) assert isinstance(products, list)
@ -127,8 +141,9 @@ def test_reverse_qol():
for item in datatype: for item in datatype:
# all items should have one of these # all items should have one of these
settlists.append(item['settings']) settlists.append(item['settings'])
# no items should have one of these # no items should have these
assert 'profiles' not in item assert 'profiles' not in item
assert 'profile_groups' not in item
for settlist in settlists: for settlist in settlists:
assert isinstance(settlist, list) assert isinstance(settlist, list)
for setting in settlist: for setting in settlist: