Skip to content

Commit 9454844

Browse files
committed
runtime/lava: add tree-based priority support
Add 4-level priority system for LAVA job queue ordering: - highest (80): manual submissions for bisection/debugging - high (60): critical trees (mainline, stable, stable-rc) - medium (40): important trees (next, stable-rt, kselftest) - low (20): other trees (default) Human submissions (submitter != SERVICE_PIPELINE) get highest priority by default, but can optionally specify priority via node['data']['priority'] using string ('high'/'medium'/'low') or numeric values (clamped to 0-100). Pipeline submissions use tree_priority from build configs. Add SERVICE_PIPELINE class constant to replace hardcoded string. Add priority field to BuildConfig class for tree priority support. Priorities are scaled to each lab's configured range before submission to accommodate different LAVA lab configurations. Signed-off-by: Ben Copeland <[email protected]>
1 parent fe827ff commit 9454844

File tree

3 files changed

+114
-10
lines changed

3 files changed

+114
-10
lines changed

kernelci/config/build.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,14 +363,14 @@ def to_yaml(cls, dumper, data):
363363
)
364364

365365

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

369369
yaml_tag = '!BuildConfig'
370370

371371
def __init__( # pylint: disable=too-many-arguments
372372
self, name, tree, branch, variants, *, reference=None,
373-
architectures=None, frequency=None
373+
architectures=None, frequency=None, priority=None
374374
):
375375
"""A build configuration defines the actual kernels to be built.
376376
@@ -388,6 +388,8 @@ def __init__( # pylint: disable=too-many-arguments
388388
389389
*frequency* is an optional string that limits how often a checkout node
390390
can be created. Format: [Nd][Nh][Nm]
391+
392+
*priority* is an optional string (high/medium/low) for tree priority.
391393
"""
392394
self._name = name
393395
self._tree = tree
@@ -396,6 +398,7 @@ def __init__( # pylint: disable=too-many-arguments
396398
self._reference = reference
397399
self._architectures = architectures
398400
self._frequency = frequency
401+
self._priority = priority
399402

400403
@classmethod
401404
# pylint: disable=arguments-differ,too-many-arguments,too-many-positional-arguments
@@ -419,6 +422,7 @@ def load_from_yaml(cls, config, name, trees, fragments, b_envs, defaults):
419422

420423
kw['architectures'] = config.get('architectures')
421424
kw['frequency'] = config.get('frequency')
425+
kw['priority'] = config.get('priority')
422426
return cls(**kw)
423427

424428
@property
@@ -460,6 +464,11 @@ def frequency(self):
460464
"""Return the checkout frequency."""
461465
return self._frequency
462466

467+
@property
468+
def priority(self):
469+
"""Return the tree priority."""
470+
return self._priority
471+
463472
@classmethod
464473
def to_yaml(cls, dumper, data):
465474
result = {

kernelci/runtime/lava.py

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,27 +325,61 @@ class LAVA(Runtime):
325325
API_VERSION = 'v0.2'
326326
RestAPIServer = namedtuple('RestAPIServer', ['url', 'session'])
327327

328+
# LAVA supports 'high'/'medium'/'low' (100/50/0), but we define our own
329+
# values to allow scaling across labs with different priority ranges.
330+
PRIORITY_HIGHEST = 80
331+
PRIORITY_HIGH = 60
332+
PRIORITY_MEDIUM = 40
333+
PRIORITY_LOW = 20
334+
335+
SERVICE_PIPELINE = 'service:pipeline'
336+
328337
def __init__(self, configs, kcictx=None, **kwargs):
329338
super().__init__(configs, **kwargs)
330339
self._context = kcictx
331340
self._server = self._connect()
332341

333342
def _get_priority(self, job):
334-
# Scale the job priority (from 0-100) within the available levels
335-
# for the lab, or use the lowest by default.
336-
priority = job.config.priority if job.config.priority else 20
343+
node = job.node
344+
submitter = node.get('submitter')
345+
346+
priority_map = {
347+
'high': self.PRIORITY_HIGH,
348+
'medium': self.PRIORITY_MEDIUM,
349+
'low': self.PRIORITY_LOW,
350+
}
351+
352+
if submitter and submitter != self.SERVICE_PIPELINE:
353+
# Human submission - check for user-specified priority
354+
user_priority = node.get('data', {}).get('priority')
355+
if user_priority:
356+
if isinstance(user_priority, str):
357+
priority = priority_map.get(user_priority.lower(), self.PRIORITY_HIGHEST)
358+
elif isinstance(user_priority, (int, float)):
359+
priority = max(0, min(100, user_priority))
360+
else:
361+
priority = self.PRIORITY_HIGHEST
362+
else:
363+
priority = self.PRIORITY_HIGHEST
364+
else:
365+
# Pipeline submission - use tree priority
366+
tree_priority = node.get('data', {}).get('tree_priority')
367+
368+
if tree_priority and isinstance(tree_priority, str):
369+
priority = priority_map.get(tree_priority.lower(), self.PRIORITY_LOW)
370+
elif tree_priority and isinstance(tree_priority, (int, float)):
371+
priority = max(0, min(100, tree_priority))
372+
else:
373+
job_priority = job.config.priority
374+
priority = job_priority if job_priority is not None else self.PRIORITY_LOW
375+
337376
if self.config.priority:
338377
priority = int(priority * self.config.priority / 100)
339378
elif (self.config.priority_max is not None and
340379
self.config.priority_min is not None):
341380
prio_range = self.config.priority_max - self.config.priority_min
342381
prio_min = self.config.priority_min
343382
priority = int((priority * prio_range / 100) + prio_min)
344-
# Increase the priority for jobs submitted by humans
345-
node = job.node
346-
submitter = node.get('submitter')
347-
if submitter and submitter != 'service:pipeline':
348-
priority = min(priority + 1, self.config.priority_max)
349383
return priority
350384

351385
def get_params(self, job, api_config=None):

tests/test_runtime.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,67 @@ def test_runtimes_init():
6868
kernelci.runtime.get_runtime(runtime_config)
6969

7070

71+
def test_lava_priority_hierarchy():
72+
"""Test LAVA priority: human=highest, tree=high/medium/low"""
73+
config = kernelci.config.load('tests/configs/lava-runtimes.yaml')
74+
runtimes = config['runtimes']
75+
runtime_config = runtimes['lab-min-12-max-40-new-runtime']
76+
lab = kernelci.runtime.get_runtime(runtime_config)
77+
78+
job_config_no_priority = type('JobConfig', (), {'priority': None})()
79+
80+
job_human_high_tree = type('Job', (), {
81+
'node': {'data': {'tree_priority': 'high'}, 'submitter': '[email protected]'},
82+
'config': job_config_no_priority
83+
})()
84+
expected_priority = int(12 + (40 - 12) * 80 / 100)
85+
assert lab._get_priority(job_human_high_tree) == expected_priority
86+
87+
job_pipeline_high_tree = type('Job', (), {
88+
'node': {'data': {'tree_priority': 'high'}, 'submitter': 'service:pipeline'},
89+
'config': job_config_no_priority
90+
})()
91+
expected_priority = int(12 + (40 - 12) * 60 / 100)
92+
assert lab._get_priority(job_pipeline_high_tree) == expected_priority
93+
94+
job_medium_tree = type('Job', (), {
95+
'node': {'data': {'tree_priority': 'medium'}, 'submitter': 'service:pipeline'},
96+
'config': job_config_no_priority
97+
})()
98+
expected_priority = int(12 + (40 - 12) * 40 / 100)
99+
assert lab._get_priority(job_medium_tree) == expected_priority
100+
101+
job_low_tree = type('Job', (), {
102+
'node': {'data': {'tree_priority': 'low'}, 'submitter': 'service:pipeline'},
103+
'config': job_config_no_priority
104+
})()
105+
expected_priority = int(12 + (40 - 12) * 20 / 100)
106+
assert lab._get_priority(job_low_tree) == expected_priority
107+
108+
job_default = type('Job', (), {
109+
'node': {'data': {}, 'submitter': 'service:pipeline'},
110+
'config': job_config_no_priority
111+
})()
112+
expected_priority = int(12 + (40 - 12) * 20 / 100)
113+
assert lab._get_priority(job_default) == expected_priority
114+
115+
# Human submission with user-specified string priority
116+
job_human_set_high = type('Job', (), {
117+
'node': {'data': {'priority': 'high'}, 'submitter': '[email protected]'},
118+
'config': job_config_no_priority
119+
})()
120+
expected_priority = int(12 + (40 - 12) * 60 / 100)
121+
assert lab._get_priority(job_human_set_high) == expected_priority
122+
123+
# Human submission with user-specified numeric priority
124+
job_human_set_numeric = type('Job', (), {
125+
'node': {'data': {'priority': 50}, 'submitter': '[email protected]'},
126+
'config': job_config_no_priority
127+
})()
128+
expected_priority = int(12 + (40 - 12) * 50 / 100)
129+
assert lab._get_priority(job_human_set_numeric) == expected_priority
130+
131+
71132
def test_lava_priority_scale():
72133
"""Test the logic for determining the priority of LAVA jobs"""
73134
config = kernelci.config.load('tests/configs/lava-runtimes.yaml')

0 commit comments

Comments
 (0)