Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions kernelci/config/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,14 +363,14 @@
)


class BuildConfig(YAMLConfigObject):
class BuildConfig(YAMLConfigObject): # pylint: disable=too-many-instance-attributes
"""Build configuration model."""

yaml_tag = '!BuildConfig'

def __init__( # pylint: disable=too-many-arguments
self, name, tree, branch, variants, *, reference=None,
architectures=None, frequency=None
architectures=None, frequency=None, priority=None
):
"""A build configuration defines the actual kernels to be built.

Expand All @@ -388,6 +388,8 @@

*frequency* is an optional string that limits how often a checkout node
can be created. Format: [Nd][Nh][Nm]

*priority* is an optional string (high/medium/low) for tree priority.
"""
self._name = name
self._tree = tree
Expand All @@ -396,9 +398,10 @@
self._reference = reference
self._architectures = architectures
self._frequency = frequency
self._priority = priority

@classmethod
# pylint: disable=arguments-differ,too-many-arguments,too-many-positional-arguments

Check warning on line 404 in kernelci/config/build.py

View workflow job for this annotation

GitHub Actions / Lint

Unknown option value for 'disable', expected a valid pylint message and got 'too-many-positional-arguments'
def load_from_yaml(cls, config, name, trees, fragments, b_envs, defaults):
"""Load BuildConfig from YAML with all references."""
kw = {'name': name}
Expand All @@ -419,6 +422,7 @@

kw['architectures'] = config.get('architectures')
kw['frequency'] = config.get('frequency')
kw['priority'] = config.get('priority')
return cls(**kw)

@property
Expand Down Expand Up @@ -460,6 +464,11 @@
"""Return the checkout frequency."""
return self._frequency

@property
def priority(self):
"""Return the tree priority."""
return self._priority

@classmethod
def to_yaml(cls, dumper, data):
result = {
Expand Down
50 changes: 42 additions & 8 deletions kernelci/runtime/lava.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,27 +325,61 @@ class LAVA(Runtime):
API_VERSION = 'v0.2'
RestAPIServer = namedtuple('RestAPIServer', ['url', 'session'])

# LAVA supports 'high'/'medium'/'low' (100/50/0), but we define our own
# values to allow scaling across labs with different priority ranges.
PRIORITY_HIGHEST = 80
PRIORITY_HIGH = 60
PRIORITY_MEDIUM = 40
PRIORITY_LOW = 20

SERVICE_PIPELINE = 'service:pipeline'

def __init__(self, configs, kcictx=None, **kwargs):
super().__init__(configs, **kwargs)
self._context = kcictx
self._server = self._connect()

def _get_priority(self, job):
# Scale the job priority (from 0-100) within the available levels
# for the lab, or use the lowest by default.
priority = job.config.priority if job.config.priority else 20
node = job.node
submitter = node.get('submitter')

priority_map = {
'high': self.PRIORITY_HIGH,
'medium': self.PRIORITY_MEDIUM,
'low': self.PRIORITY_LOW,
}

if submitter and submitter != self.SERVICE_PIPELINE:
# Human submission - check for user-specified priority
user_priority = node.get('data', {}).get('priority')
if user_priority:
if isinstance(user_priority, str):
priority = priority_map.get(user_priority.lower(), self.PRIORITY_HIGHEST)
elif isinstance(user_priority, (int, float)):
priority = max(0, min(100, user_priority))
else:
priority = self.PRIORITY_HIGHEST
else:
priority = self.PRIORITY_HIGHEST
else:
# Pipeline submission - use tree priority
tree_priority = node.get('data', {}).get('tree_priority')

if tree_priority and isinstance(tree_priority, str):
priority = priority_map.get(tree_priority.lower(), self.PRIORITY_LOW)
elif tree_priority and isinstance(tree_priority, (int, float)):
priority = max(0, min(100, tree_priority))
else:
job_priority = job.config.priority
priority = job_priority if job_priority is not None else self.PRIORITY_LOW

if self.config.priority:
priority = int(priority * self.config.priority / 100)
elif (self.config.priority_max is not None and
self.config.priority_min is not None):
prio_range = self.config.priority_max - self.config.priority_min
prio_min = self.config.priority_min
priority = int((priority * prio_range / 100) + prio_min)
# Increase the priority for jobs submitted by humans
node = job.node
submitter = node.get('submitter')
if submitter and submitter != 'service:pipeline':
priority = min(priority + 1, self.config.priority_max)
return priority

def get_params(self, job, api_config=None):
Expand Down
61 changes: 61 additions & 0 deletions tests/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,67 @@ def test_runtimes_init():
kernelci.runtime.get_runtime(runtime_config)


def test_lava_priority_hierarchy():
"""Test LAVA priority: human=highest, tree=high/medium/low"""
config = kernelci.config.load('tests/configs/lava-runtimes.yaml')
runtimes = config['runtimes']
runtime_config = runtimes['lab-min-12-max-40-new-runtime']
lab = kernelci.runtime.get_runtime(runtime_config)

job_config_no_priority = type('JobConfig', (), {'priority': None})()

job_human_high_tree = type('Job', (), {
'node': {'data': {'tree_priority': 'high'}, 'submitter': '[email protected]'},
'config': job_config_no_priority
})()
expected_priority = int(12 + (40 - 12) * 80 / 100)
assert lab._get_priority(job_human_high_tree) == expected_priority

job_pipeline_high_tree = type('Job', (), {
'node': {'data': {'tree_priority': 'high'}, 'submitter': 'service:pipeline'},
'config': job_config_no_priority
})()
expected_priority = int(12 + (40 - 12) * 60 / 100)
assert lab._get_priority(job_pipeline_high_tree) == expected_priority

job_medium_tree = type('Job', (), {
'node': {'data': {'tree_priority': 'medium'}, 'submitter': 'service:pipeline'},
'config': job_config_no_priority
})()
expected_priority = int(12 + (40 - 12) * 40 / 100)
assert lab._get_priority(job_medium_tree) == expected_priority

job_low_tree = type('Job', (), {
'node': {'data': {'tree_priority': 'low'}, 'submitter': 'service:pipeline'},
'config': job_config_no_priority
})()
expected_priority = int(12 + (40 - 12) * 20 / 100)
assert lab._get_priority(job_low_tree) == expected_priority

job_default = type('Job', (), {
'node': {'data': {}, 'submitter': 'service:pipeline'},
'config': job_config_no_priority
})()
expected_priority = int(12 + (40 - 12) * 20 / 100)
assert lab._get_priority(job_default) == expected_priority

# Human submission with user-specified string priority
job_human_set_high = type('Job', (), {
'node': {'data': {'priority': 'high'}, 'submitter': '[email protected]'},
'config': job_config_no_priority
})()
expected_priority = int(12 + (40 - 12) * 60 / 100)
assert lab._get_priority(job_human_set_high) == expected_priority

# Human submission with user-specified numeric priority
job_human_set_numeric = type('Job', (), {
'node': {'data': {'priority': 50}, 'submitter': '[email protected]'},
'config': job_config_no_priority
})()
expected_priority = int(12 + (40 - 12) * 50 / 100)
assert lab._get_priority(job_human_set_numeric) == expected_priority


def test_lava_priority_scale():
"""Test the logic for determining the priority of LAVA jobs"""
config = kernelci.config.load('tests/configs/lava-runtimes.yaml')
Expand Down