From 858180a2383432f99d60299fc6c1ddf00db1af1d Mon Sep 17 00:00:00 2001 From: Ben Copeland Date: Mon, 24 Nov 2025 12:22:38 +0000 Subject: [PATCH 1/2] docs: connecting-pull-lab Update the docs on the guide on how to execute locally. Signed-off-by: Ben Copeland --- doc/connecting-pull-lab.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/doc/connecting-pull-lab.md b/doc/connecting-pull-lab.md index 54fafe3bd..76dc01a78 100644 --- a/doc/connecting-pull-lab.md +++ b/doc/connecting-pull-lab.md @@ -1,5 +1,5 @@ --- -title: "Connecting Pull Labs runtime" +title: "Connecting and Running Pull Labs runtime" date: 2025-02-14 description: "Connecting a PULL_LABS compatible lab to the KernelCI pipeline" weight: 4 @@ -9,6 +9,9 @@ KernelCI supports labs that follow the [PULL_LABS protocol](https://github.com/k LAVA- and Kubernetes-based integrations. This guide shows the minimum configuration needed to make a lab instance visible to the pipeline. +There is an payload script in `tools/example_pull_lab.py` to which +provides a simply way to execute these pull-lab payloads. + The examples below mirror the demo entries committed in this repository. Replace the names and tokens with the values that match your deployment. @@ -90,3 +93,32 @@ Ensure the `scheduler` service is started with the `--runtimes pull-labs-demo` argument in the relevant `docker-compose` file so the new runtime becomes active. The lab will see the generated events once it authenticates with the callback token value paired with the token name defined in the pipeline configuration. + +## Running the Example Pull Lab Script + +The `tools/example_pull_lab.py` script provides a simple way to execute pull-lab +payloads using tuxrun for QEMU-based virtual targets. + +### Prerequisites + +Tuxrun is required to run the jobs, Tuxrun requires podman also to be setup to +execute the jobs. + +- Install tuxrun: `pip install tuxrun` +- Install podman: `sudo apt install podman` +- Tuxrun handles downloads and QEMU VM execution automatically + +### Running the Script + +```bash +python tools/example_pull_lab.py +``` + +The script will: +- Detect architecture from job definitions +- Support filtering by platform, group, device, and runtime +- Use `--cache-dir` for storing caches and outputs in `./tuxrun-cache/` +- Saves output to timestamped directories in `./test_output/` + +**TODO:** Support for FVP (Fixed Virtual Platform) and DUT (Device Under Test) +jobs will be added in future versions, along with publishing to KCIDB. From 08aa42ee915af449255dcc9aa380db05f4cfc33e Mon Sep 17 00:00:00 2001 From: Ben Copeland Date: Mon, 24 Nov 2025 12:24:17 +0000 Subject: [PATCH 2/2] tools/example_pull_lab.py: Update script to store log output The script will now store all LAVA outputs to ./test_output/*. We use the date/job_id for the location storing, and have the ability to add --no-save-ouputs if required. Also add this folder to .gitignore. Future version will publish to KCIDB but this gets us in the right direction. Signed-off-by: Ben Copeland --- .gitignore | 1 + tools/example_pull_lab.py | 40 +++++++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 1d203336f..60c3262ff 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ data *.pyc *.venv *.log +test_output/ diff --git a/tools/example_pull_lab.py b/tools/example_pull_lab.py index a620c3859..8245acb13 100755 --- a/tools/example_pull_lab.py +++ b/tools/example_pull_lab.py @@ -12,6 +12,8 @@ import time import subprocess import shlex +import re +from urllib.parse import urlparse BASE_URI = "https://staging.kernelci.org:9000/latest" @@ -38,6 +40,19 @@ def retrieve_job_definition(url): return response.json() +def parse_job_definition_url(url): + """Extract date and job ID from job definition URL.""" + try: + parsed = urlparse(url) + path = parsed.path + match = re.search(r'/pull_labs_jobs/(\d{8})/([a-f0-9]+)\.json', path) + if match: + return match.group(1), match.group(2) + except Exception as e: + print(f"Failed to parse job definition URL: {e}") + return None, None + + def run_tuxrun(kernel_url, modules_url, device="qemu-x86_64", tests=None, rootfs_url=None, cache_dir=None): """ Launch a test using tuxrun @@ -67,8 +82,8 @@ def run_tuxrun(kernel_url, modules_url, device="qemu-x86_64", tests=None, rootfs if cache_dir: os.makedirs(cache_dir, exist_ok=True) - cmd.extend(["--save-outputs", "--cache-dir", cache_dir]) - print(f"Outputs will be saved to: {cache_dir}") + cmd.extend(["--save-outputs", "--cache-dir", cache_dir, "--log-file", "-"]) + print(f"āœ“ Outputs will be saved to: {cache_dir}") print(f"Executing command: {' '.join(shlex.quote(arg) for arg in cmd)}") @@ -80,7 +95,7 @@ def run_tuxrun(kernel_url, modules_url, device="qemu-x86_64", tests=None, rootfs return result.returncode except (subprocess.CalledProcessError, FileNotFoundError) as e: print(f"\nāœ— Error running tuxrun: {e}") - return e.returncode + return e.returncode if hasattr(e, 'returncode') else 1 def prepare_and_run(artifacts, device="qemu-x86_64", tests=None, rootfs_override=None, cache_dir=None): @@ -96,7 +111,6 @@ def prepare_and_run(artifacts, device="qemu-x86_64", tests=None, rootfs_override """ kernel_url = artifacts.get("kernel") modules_url = artifacts.get("modules") - # Try rootfs first, then ramdisk as fallback rootfs_url = rootfs_override if rootfs_override else (artifacts.get("rootfs") or artifacts.get("ramdisk")) if not kernel_url or not modules_url: @@ -137,7 +151,13 @@ def main(): ) parser.add_argument( "--cache-dir", - help="Directory to save tuxrun outputs and cache (e.g., ./outputs). Enables --save-outputs flag.", + default="./test_output", + help="Directory to save tuxrun outputs and cache (default: ./test_output). Use --no-save-outputs to disable.", + ) + parser.add_argument( + "--no-save-outputs", + action="store_true", + help="Disable saving outputs (overrides --cache-dir)", ) parser.add_argument( "--platform", @@ -242,9 +262,13 @@ def main(): continue cache_dir = None - if args.cache_dir: - node_id = node.get("id", "unknown") - cache_dir = os.path.join(args.cache_dir, node_id) + if not args.no_save_outputs and args.cache_dir: + date, job_id = parse_job_definition_url(job_definition_url) + if date and job_id: + cache_dir = os.path.join(args.cache_dir, date, job_id) + else: + node_id = node.get("id", "unknown") + cache_dir = os.path.join(args.cache_dir, node_id) prepare_and_run( job_artifacts,