diff --git a/config/runtime/README.md b/config/runtime/README.md new file mode 100644 index 0000000000..f268708d20 --- /dev/null +++ b/config/runtime/README.md @@ -0,0 +1,39 @@ +# Runtime templates + +## QEMU container image for LAVA + +The `boot/qemu.jinja2` template uses `kernelci/qemu` by default. To use a +mirror or another compatible QEMU image, set `qemu_docker_image` in a +platform's `params`: + +```yaml +platforms: + qemu-arm64: + arch: arm64 + boot_method: qemu + context: + arch: arm64 + params: + qemu_docker_image: registry.example.com:5000/kernelci/qemu:stable +``` + +A job can also set the parameter. Job parameters take precedence over +platform parameters: + +```yaml +jobs: + baseline: + template: base/lava.jinja2 + params: + boot_commands: ramdisk + test_method: baseline + ramdiskroot: https://example.com/rootfs + qemu_docker_image: registry.example.com/kernelci/qemu:stable +``` + +Use a complete container image reference, including an optional registry, +port, tag or digest. An omitted, null or empty value uses `kernelci/qemu`. +The selected image must provide the platform's `qemu-system-` binary +and be accessible to the LAVA worker. This parameter selects the boot +action's container image; it is separate from the job's top-level `image` +setting for its runtime environment. diff --git a/config/runtime/base/lava.jinja2 b/config/runtime/base/lava.jinja2 index 852be98714..e372b21249 100644 --- a/config/runtime/base/lava.jinja2 +++ b/config/runtime/base/lava.jinja2 @@ -13,7 +13,6 @@ # * kernel image name # * UEFI binaries for QEMU etc. # * rootfs URL -# * Docker image for QEMU job_name: "[{{ instanceid|default('KernelCI AKS')}}] {{ node.id }} {{ node.name }} {{ node.data.kernel_revision.describe }}" diff --git a/config/runtime/boot/qemu.jinja2 b/config/runtime/boot/qemu.jinja2 index d427649fa8..8146658aff 100644 --- a/config/runtime/boot/qemu.jinja2 +++ b/config/runtime/boot/qemu.jinja2 @@ -34,7 +34,7 @@ - boot: docker: binary: qemu-system-{{ platform_config.arch }} - image: kernelci/qemu + image: '{{ qemu_docker_image | default("kernelci/qemu", true) }}' media: tmpfs method: qemu failure_retry: 3 diff --git a/tests/test_runtime_qemu.py b/tests/test_runtime_qemu.py new file mode 100644 index 0000000000..4ec614ee35 --- /dev/null +++ b/tests/test_runtime_qemu.py @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Copyright (C) 2026 Jiakai Xu + +"""QEMU container image configuration in generated LAVA jobs.""" + +import pytest +import yaml + +from kernelci.config.job import Job as JobConfig +from kernelci.config.platform import Platform +from kernelci.config.runtime import RuntimeLAVA +from kernelci.runtime import Job +from kernelci.runtime.lava import LAVA + + +@pytest.mark.parametrize("arch", ["arm64", "x86_64"]) +@pytest.mark.parametrize("boot_commands", ["ramdisk", "rootfs"]) +@pytest.mark.parametrize( + "platform_params,job_params,expected_image", + [ + pytest.param({}, {}, "kernelci/qemu", id="default"), + pytest.param( + {"qemu_docker_image": "registry.example.com:5000/qemu:stable"}, + {}, + "registry.example.com:5000/qemu:stable", + id="platform", + ), + pytest.param( + {}, + {"qemu_docker_image": "registry.example.com/qemu:latest"}, + "registry.example.com/qemu:latest", + id="job", + ), + pytest.param( + {"qemu_docker_image": "registry.example.com/qemu:platform"}, + {"qemu_docker_image": "registry.example.com/qemu:job"}, + "registry.example.com/qemu:job", + id="job-overrides-platform", + ), + pytest.param( + {}, {"qemu_docker_image": None}, "kernelci/qemu", id="null" + ), + pytest.param( + {}, {"qemu_docker_image": ""}, "kernelci/qemu", id="empty" + ), + pytest.param( + {}, + { + "qemu_docker_image": "registry.example.com/qemu@sha256:" + + "a" * 64 + }, + "registry.example.com/qemu@sha256:" + "a" * 64, + id="digest", + ), + ], +) +def test_qemu_docker_image( + arch, boot_commands, platform_params, job_params, expected_image, mocker +): + """Render platform/job overrides through the real LAVA runtime offline.""" + mocker.patch( + "requests.sessions.Session.request", + side_effect=AssertionError("Job generation must not contact a service"), + ) + config = JobConfig( + name="baseline", + template="base/lava.jinja2", + params={ + "boot_commands": boot_commands, + "test_method": "baseline", + "ramdiskroot": "https://example.com/rootfs", + "ramdisk": "https://example.com/rootfs.cpio.gz", + **job_params, + }, + ) + job = Job( + { + "id": "qemu-test", + "name": "baseline", + "data": {"kernel_revision": {"describe": "v6.12"}}, + "artifacts": { + "kernel": "https://example.com/Image", + "modules": "https://example.com/modules.tar.xz", + }, + }, + config, + ) + job.platform_config = Platform( + name=f"qemu-{arch}", + arch=arch, + boot_method="qemu", + context={"arch": arch}, + params=platform_params, + ) + runtime = LAVA(RuntimeLAVA(name="test-lab", lab_type="lava")) + + definition = yaml.safe_load(runtime.generate(job, runtime.get_params(job))) + + assert definition["actions"][1]["boot"]["docker"] == { + "binary": f"qemu-system-{arch}", + "image": expected_image, + }