diff --git a/kernelci/config/build.py b/kernelci/config/build.py index aa3c0c0cfa..9a91135991 100644 --- a/kernelci/config/build.py +++ b/kernelci/config/build.py @@ -363,14 +363,14 @@ def to_yaml(cls, dumper, data): ) -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. @@ -388,6 +388,8 @@ def __init__( # pylint: disable=too-many-arguments *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 @@ -396,6 +398,7 @@ def __init__( # pylint: disable=too-many-arguments self._reference = reference self._architectures = architectures self._frequency = frequency + self._priority = priority @classmethod # 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): kw['architectures'] = config.get('architectures') kw['frequency'] = config.get('frequency') + kw['priority'] = config.get('priority') return cls(**kw) @property @@ -460,6 +464,11 @@ def frequency(self): """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 = { diff --git a/kernelci/runtime/lava.py b/kernelci/runtime/lava.py index 48f293036a..c7a231727d 100644 --- a/kernelci/runtime/lava.py +++ b/kernelci/runtime/lava.py @@ -325,15 +325,54 @@ 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 @@ -341,11 +380,6 @@ def _get_priority(self, job): 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): diff --git a/tests/test_runtime.py b/tests/test_runtime.py index 592c43697b..efc8842f6a 100644 --- a/tests/test_runtime.py +++ b/tests/test_runtime.py @@ -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': 'user@example.com'}, + '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': 'user@example.com'}, + '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': 'user@example.com'}, + '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')