Skip to content

Commit a1ed0cd

Browse files
committed
runtime: Add Pull-Only labs runtime implementation
New PULL_LABS runtime for KernelCI that implements the Job Definition creation according to the PULL_LABS protocol specification. New files: kernelci/runtime/pull_labs.py - Main runtime implementation config/runtime/base/pull_labs.jinja2 - JSON job definition template kernelci/config/runtime.py - Added RuntimePullLabs configuration class Properties: poll_interval, timeout, storage_name Registered in RuntimeFactory._lab_types as 'pull_labs' Signed-off-by: Denys Fedoryshchenko <[email protected]>
1 parent f463d76 commit a1ed0cd

File tree

3 files changed

+619
-45
lines changed

3 files changed

+619
-45
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
{# SPDX-License-Identifier: LGPL-2.1-or-later -#}
2+
{# PULL_LABS job definition template - generates JSON format #}
3+
{# This template creates a job definition according to PULL_LABS protocol Section 3 #}
4+
{
5+
"artifacts": {
6+
{%- if node.artifacts.kernel %}
7+
"kernel": "{{ node.artifacts.kernel }}"
8+
{%- endif %}
9+
{%- if node.artifacts.modules %},
10+
"modules": "{{ node.artifacts.modules }}"
11+
{%- endif %}
12+
{%- if node.artifacts.kselftest %},
13+
"kselftest": "{{ node.artifacts.kselftest }}"
14+
{%- endif %}
15+
{%- if node.artifacts.rootfs %},
16+
"rootfs_type": "{{ node.artifacts.rootfs }}"
17+
{%- endif %}
18+
{%- if node.artifacts.initrd %},
19+
"initrd": "{{ node.artifacts.initrd }}"
20+
{%- endif %}
21+
{%- if node.artifacts.dtb %},
22+
"dtb": "{{ node.artifacts.dtb }}"
23+
{%- endif %}
24+
},
25+
"tests": [
26+
{%- if test_definitions %}
27+
{%- for test in test_definitions %}
28+
{
29+
"id": "{{ test.id }}",
30+
"type": "{{ test.type }}",
31+
"depends": {{ test.depends | tojson }},
32+
"timeout_s": {{ test.timeout_s | default(600) }}
33+
{%- if test.subset %},
34+
"subset": {{ test.subset | tojson }}
35+
{%- endif %}
36+
{%- if test.parameters %},
37+
"parameters": "{{ test.parameters }}"
38+
{%- endif %}
39+
{%- if test.pre_commands %},
40+
"pre-commands": {{ test.pre_commands | tojson }}
41+
{%- else %},
42+
"pre-commands": []
43+
{%- endif %}
44+
{%- if test.post_commands %},
45+
"post-commands": {{ test.post_commands | tojson }}
46+
{%- else %},
47+
"post-commands": []
48+
{%- endif %}
49+
}{{ "," if not loop.last else "" }}
50+
{%- endfor %}
51+
{%- else %}
52+
{
53+
"id": "boot-test-{{ node.id[:8] }}",
54+
"type": "boot",
55+
"depends": [],
56+
"timeout_s": 600,
57+
"pre-commands": [],
58+
"post-commands": []
59+
}
60+
{%- endif %}
61+
],
62+
"environment": {
63+
"platform": "{{ platform_config.name }}",
64+
"arch": "{{ platform_config.arch }}",
65+
"console": {
66+
"method": "{{ platform_config.console_method | default('serial') }}",
67+
"baud": {{ platform_config.console_baud | default(115200) }}
68+
}
69+
{%- if platform_config.params and platform_config.params.requirements %}
70+
,"requirements": {{ platform_config.params.requirements | tojson }}
71+
{%- endif %}
72+
},
73+
"callback": {
74+
"url": "{{ instance_callback }}/node/{{ node.id }}",
75+
"token_name": "{{ callback_token_name | default('kernelci-pipeline-callback') }}"
76+
}
77+
{%- if node.artifacts.metadata %}
78+
,"integrity": {
79+
"sha256": {
80+
{%- if checksums %}
81+
{%- for artifact_name, checksum in checksums.items() %}
82+
"{{ artifact_name }}": "{{ checksum }}"{{ "," if not loop.last else "" }}
83+
{%- endfor %}
84+
{%- else %}
85+
"_comment": "Checksums would be populated from metadata.json if available"
86+
{%- endif %}
87+
}
88+
}
89+
{%- endif %}
90+
}

kernelci/config/runtime.py

Lines changed: 89 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#
33
# Copyright (C) 2019, 2021-2023 Collabora Limited
44
# Author: Guillaume Tucker <[email protected]>
5+
#
6+
# Copyright (C) 2025 Collabora Limited
7+
# Author: Denys Fedoryshchenko <[email protected]>
58

69
"""KernelCI Runtime environment configuration"""
710

@@ -11,7 +14,7 @@
1114
class Runtime(YAMLConfigObject):
1215
"""Runtime environment configuration"""
1316

14-
yaml_tag = '!Runtime'
17+
yaml_tag = "!Runtime"
1518

1619
def __init__(self, name, lab_type, filters=None, rules=None):
1720
"""A runtime environment configuration object
@@ -44,25 +47,24 @@ def rules(self):
4447
@classmethod
4548
def _get_yaml_attributes(cls):
4649
attrs = super()._get_yaml_attributes()
47-
attrs.update({'lab_type', 'rules'})
50+
attrs.update({"lab_type", "rules"})
4851
return attrs
4952

5053
@classmethod
5154
def _to_yaml_dict(cls, data):
52-
yaml_dict = {
53-
key: getattr(data, key)
54-
for key in cls._get_yaml_attributes()
55-
}
56-
yaml_dict.update({
57-
# pylint: disable=protected-access
58-
'filters': [{fil.name: fil} for fil in data._filters],
59-
})
55+
yaml_dict = {key: getattr(data, key) for key in cls._get_yaml_attributes()}
56+
yaml_dict.update(
57+
{
58+
# pylint: disable=protected-access
59+
"filters": [{fil.name: fil} for fil in data._filters],
60+
}
61+
)
6062
return yaml_dict
6163

6264
@classmethod
6365
def to_yaml(cls, dumper, data):
6466
return dumper.represent_mapping(
65-
'tag:yaml.org,2002:map', cls._to_yaml_dict(data)
67+
"tag:yaml.org,2002:map", cls._to_yaml_dict(data)
6668
)
6769

6870
def match(self, data):
@@ -73,19 +75,26 @@ def match(self, data):
7375
class RuntimeLAVA(Runtime):
7476
"""Configuration for LAVA runtime environments"""
7577

76-
yaml_tag = '!RuntimeLAVA'
78+
yaml_tag = "!RuntimeLAVA"
7779

7880
PRIORITIES = {
79-
'low': 0,
80-
'medium': 50,
81-
'high': 100,
81+
"low": 0,
82+
"medium": 50,
83+
"high": 100,
8284
}
8385

8486
# This should be solved by dropping the "priority" attribute
8587
# pylint: disable=too-many-arguments
86-
def __init__(self, url=None, priority=None, priority_min=None,
87-
priority_max=None, queue_timeout=None, notify=None,
88-
**kwargs):
88+
def __init__(
89+
self,
90+
url=None,
91+
priority=None,
92+
priority_min=None,
93+
priority_max=None,
94+
queue_timeout=None,
95+
notify=None,
96+
**kwargs,
97+
):
8998
super().__init__(**kwargs)
9099

91100
def _set_priority_value(value, default):
@@ -135,24 +144,25 @@ def notify(self):
135144
@classmethod
136145
def _get_yaml_attributes(cls):
137146
attrs = super()._get_yaml_attributes()
138-
attrs.update({
139-
'priority',
140-
'priority_min',
141-
'priority_max',
142-
'queue_timeout',
143-
'url',
144-
'notify',
145-
})
147+
attrs.update(
148+
{
149+
"priority",
150+
"priority_min",
151+
"priority_max",
152+
"queue_timeout",
153+
"url",
154+
"notify",
155+
}
156+
)
146157
return attrs
147158

148159

149160
class RuntimeDocker(Runtime):
150161
"""Configuration for Docker runtime environments"""
151162

152-
yaml_tag = '!RuntimeDocker'
163+
yaml_tag = "!RuntimeDocker"
153164

154-
def __init__(self, env_file=None, volumes=None, user=None, timeout=None,
155-
**kwargs):
165+
def __init__(self, env_file=None, volumes=None, user=None, timeout=None, **kwargs):
156166
super().__init__(**kwargs)
157167
self._env_file = env_file
158168
self._volumes = volumes or []
@@ -182,14 +192,14 @@ def timeout(self):
182192
@classmethod
183193
def _get_yaml_attributes(cls):
184194
attrs = super()._get_yaml_attributes()
185-
attrs.update({'env_file', 'volumes', 'user', 'timeout'})
195+
attrs.update({"env_file", "volumes", "user", "timeout"})
186196
return attrs
187197

188198

189199
class RuntimeKubernetes(Runtime):
190200
"""Configuration for Kubernetes runtime environments"""
191201

192-
yaml_tag = '!RuntimeKubernetes'
202+
yaml_tag = "!RuntimeKubernetes"
193203

194204
def __init__(self, context=None, **kwargs):
195205
super().__init__(**kwargs)
@@ -203,43 +213,77 @@ def context(self):
203213
@classmethod
204214
def _get_yaml_attributes(cls):
205215
attrs = super()._get_yaml_attributes()
206-
attrs.update({'context'})
216+
attrs.update({"context"})
217+
return attrs
218+
219+
220+
class RuntimePullLabs(Runtime):
221+
"""Configuration for PULL_LABS runtime environments"""
222+
223+
yaml_tag = "!RuntimePullLabs"
224+
225+
def __init__(self, poll_interval=None, timeout=None, storage_name=None, **kwargs):
226+
super().__init__(**kwargs)
227+
self._poll_interval = poll_interval or 30
228+
self._timeout = timeout or 3600
229+
self._storage_name = storage_name
230+
231+
@property
232+
def poll_interval(self):
233+
"""Polling interval for events in seconds"""
234+
return self._poll_interval
235+
236+
@property
237+
def timeout(self):
238+
"""Job timeout in seconds"""
239+
return self._timeout
240+
241+
@property
242+
def storage_name(self):
243+
"""Storage configuration name for job definitions"""
244+
return self._storage_name
245+
246+
@classmethod
247+
def _get_yaml_attributes(cls):
248+
attrs = super()._get_yaml_attributes()
249+
attrs.update({"poll_interval", "timeout", "storage_name"})
207250
return attrs
208251

209252

210253
class RuntimeFactory: # pylint: disable=too-few-public-methods
211254
"""Factory to create lab objects from YAML data."""
212255

213256
_lab_types = {
214-
'docker': RuntimeDocker,
215-
'kubernetes': RuntimeKubernetes,
216-
'lava': RuntimeLAVA,
217-
'legacy.lava_xmlrpc': RuntimeLAVA,
218-
'legacy.lava_rest': RuntimeLAVA,
219-
'shell': Runtime,
257+
"docker": RuntimeDocker,
258+
"kubernetes": RuntimeKubernetes,
259+
"lava": RuntimeLAVA,
260+
"legacy.lava_xmlrpc": RuntimeLAVA,
261+
"legacy.lava_rest": RuntimeLAVA,
262+
"pull_labs": RuntimePullLabs,
263+
"shell": Runtime,
220264
}
221265

222266
@classmethod
223267
def from_yaml(cls, name, config, default_filters):
224268
"""Load the configuration from YAML data"""
225-
lab_type = config.get('lab_type')
269+
lab_type = config.get("lab_type")
226270
kwargs = {
227-
'name': name,
228-
'lab_type': lab_type,
229-
'filters': FilterFactory.from_data(config, default_filters),
271+
"name": name,
272+
"lab_type": lab_type,
273+
"filters": FilterFactory.from_data(config, default_filters),
230274
}
231275
lab_cls = cls._lab_types[lab_type] if lab_type else Runtime
232276
return lab_cls.load_from_yaml(config, **kwargs)
233277

234278

235279
def from_yaml(data, filters):
236280
"""Load the runtime environment from YAML based on its type"""
237-
runtimes_filters = filters.get('runtimes')
281+
runtimes_filters = filters.get("runtimes")
238282
runtimes = {
239283
name: RuntimeFactory.from_yaml(name, runtime, runtimes_filters)
240-
for name, runtime in data.get('runtimes', {}).items()
284+
for name, runtime in data.get("runtimes", {}).items()
241285
}
242286

243287
return {
244-
'runtimes': runtimes,
288+
"runtimes": runtimes,
245289
}

0 commit comments

Comments
 (0)