Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/itf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
- name: Install qemu
run: |
sudo apt-get update
sudo apt-get install -y qemu-system-arm
sudo apt-get install -y qemu-system-arm qemu-system-x86 cloud-image-utils
- name: Enable KVM group perms
run: |
sudo chmod 666 /dev/kvm
Expand Down
16 changes: 16 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,19 @@ http_file(
sha256 = "9ca3891b27e4b7bbf6c519d8924283e89c03b62513a988f751a3ee3d10c293e4",
urls = ["https://github.com/elektrobit/eb_corbos_toolkit/releases/download/v2.0.0-beta1/fastdev-ubuntu-ebclfsa-ebcl-qemuarm64.tar.gz"],
)

###############################################################################
#
# Ubuntu minimal cloud image for x86_64 Linux QEMU integration tests
#
# Pinned to a dated release directory so the download stays reproducible. The
# minimal image is used instead of the full server image to reduce test times.
# The file uses the ".img" extension but is in fact a qcow2 image.
#
###############################################################################
http_file(
name = "ubuntu_noble_minimal_cloudimg_amd64",
downloaded_file_path = "ubuntu-24.04-minimal-cloudimg-amd64.img",
sha256 = "b3064efb500d71d6ccbe619b1716062b803e285116e040627b430aaee14cced6",
urls = ["https://cloud-images.ubuntu.com/minimal/releases/noble/release-20260801/ubuntu-24.04-minimal-cloudimg-amd64.img"],
)
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,51 @@ QEMU targets are configured using a JSON configuration file that specifies netwo
}
```

#### Linux QEMU images

The repository includes two image build flows that are meant to be consumed by QEMU tests.
These take a base image and customize it for ITF testing. The resulting images are stored in the Bazel cache and can be used in tests.

For Ubuntu image building take a look at `test/resources/ubuntu_x86_64/BUILD` and for Ebclfsa image building take a look at `test/resources/ebclfsa_aarch64/BUILD`.
Using the prepared images, run your ITF tests.

Ubuntu:

```starlark
py_itf_test(
name = "test_qemu_ubuntu",
srcs = ["test_qemu_ubuntu.py"],
args = [
"--qemu-rootfs=$(location //test/resources/ubuntu_x86_64:image)",
"--qemu-config=$(location //test/resources/ubuntu_x86_64:qemu_config)",
],
data = [
"//test/resources/ubuntu_x86_64:image",
"//test/resources/ubuntu_x86_64:qemu_config",
],
plugins = ["@score_itf//score/itf/plugins:qemu_plugin"],
)
```

Ebclfsa:

```starlark
py_itf_test(
name = "test_qemu_ebclfsa",
srcs = ["test_qemu_ebclfsa.py"],
args = [
"--qemu-rootfs=$(location //test/resources/ebclfsa_aarch64:image)",
"--qemu-kernel=$(location //test/resources/ebclfsa_aarch64:kernel)",
"--qemu-config=$(location //test/resources/ebclfsa_aarch64:qemu_config)",
],
data = [
"//test/resources/ebclfsa_aarch64:image",
"//test/resources/ebclfsa_aarch64:kernel",
"//test/resources/ebclfsa_aarch64:qemu_config",
],
plugins = ["@score_itf//score/itf/plugins:qemu_plugin"],
)
```

### Capability-Based Tests

Expand Down Expand Up @@ -442,9 +487,9 @@ bazel test //test:test_docker \
### QEMU Tests

```bash
# With pre-built QEMU image
# With a pre-built QEMU root filesystem image
bazel test //test:test_qemu \
--test_arg="--qemu-image=/path/to/kernel.img"
--test_arg="--qemu-rootfs=/path/to/rootfs.img"
Comment thread
lurtz marked this conversation as resolved.
```

## QEMU Setup (Linux)
Expand Down
9 changes: 9 additions & 0 deletions scripts/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@ sh_binary(
],
visibility = ["//visibility:public"],
)

exports_files(
[
"build_ebclfsa_aarch64_image.sh",
"build_ubuntu_x86_64_image.sh",
"render_user_data.py",
],
visibility = ["//visibility:public"],
)
122 changes: 122 additions & 0 deletions scripts/build_ubuntu_x86_64_image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/bin/bash
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
#
# Turns a stock Ubuntu cloud image into an image that can be driven by the score
# ITF QEMU plugin.
#
# The image is booted exactly once with a cloud-init NoCloud seed image attached.
# cloud-init applies the configuration from a rendered cloud-init/user-data
# derived from qemu_config.json, then powers the machine off again, so this
# script simply has to wait for QEMU to exit.
#
# Usage: build_ubuntu_x86_64_image.sh <working dir> <source image> <target image> <user-data> <meta-data> <qemu-config> <user-data-renderer>

set -euxo pipefail

if [[ $# -ne 7 ]]; then
echo "Error: Expected 7 arguments (working directory, source image, target image, cloud-init user-data, cloud-init meta-data, qemu config, user-data renderer)" >&2
exit 1
fi

WORKING_DIR="$1"
IMAGE_SOURCE="$2"
IMAGE_TARGET="$3"
USER_DATA="$4"
META_DATA="$5"
QEMU_CONFIG="$6"
USER_DATA_RENDERER="$7"

# Size of the resulting disk. The stock cloud image ships a small virtual disk;
# growing it leaves room for artifacts uploaded by tests.
IMAGE_SIZE="8G"
# Upper bound for the customization boot. Generous because the build machine may
# not provide KVM, in which case QEMU falls back to TCG emulation.
BOOT_TIMEOUT_SECONDS=1200

for tool in qemu-system-x86_64 qemu-img cloud-localds python3; do
if ! command -v "${tool}" &>/dev/null; then
echo "Error: ${tool} is not installed. Please install it to proceed." >&2
exit 1
fi
done

if [[ ! -f "${IMAGE_SOURCE}" ]]; then
echo "Error: Image source is not a file: ${IMAGE_SOURCE}" >&2
exit 1
fi

for f in "${USER_DATA}" "${META_DATA}"; do
if [[ ! -f "${f}" ]]; then
echo "Error: cloud-init file does not exist: ${f}" >&2
exit 1
fi
done

if [[ ! -f "${QEMU_CONFIG}" ]]; then
echo "Error: QEMU config does not exist: ${QEMU_CONFIG}" >&2
exit 1
fi

if [[ ! -f "${USER_DATA_RENDERER}" ]]; then
echo "Error: user-data renderer does not exist: ${USER_DATA_RENDERER}" >&2
exit 1
fi

mkdir -p "$(dirname "${IMAGE_TARGET}")"
rm -f "${IMAGE_TARGET}"
cp -L "${IMAGE_SOURCE}" "${IMAGE_TARGET}"

# The image file must be writable, both for the customization boot below and for
# the qemu overlay handling in the ITF QEMU plugin.
chmod +w "${IMAGE_TARGET}"
qemu-img resize "${IMAGE_TARGET}" "${IMAGE_SIZE}"

SEED_IMAGE="${WORKING_DIR}/score-itf-seed.img"
USER_DATA_RENDERED="${WORKING_DIR}/score-itf-user-data.yaml"
rm -f "${SEED_IMAGE}"

# Keep cloud-init network settings in sync with runtime QEMU settings.
python3 "${USER_DATA_RENDERER}" "${QEMU_CONFIG}" "${USER_DATA}" "${USER_DATA_RENDERED}"

cloud-localds "${SEED_IMAGE}" "${USER_DATA_RENDERED}" "${META_DATA}"

QEMU_LOG="${WORKING_DIR}/qemu_customization.log"

# -no-reboot makes QEMU exit instead of restarting, should cloud-init decide to
# reboot rather than power off.
timeout "${BOOT_TIMEOUT_SECONDS}" qemu-system-x86_64 \
-accel kvm -accel tcg \
-machine pc \
-cpu Cascadelake-Server-v5 \
-smp 2 \
-m 2048 \
-no-reboot \
-device virtio-blk-pci,drive=vd0 -drive if=none,format=qcow2,file="${IMAGE_TARGET}",id=vd0 \
-device virtio-blk-pci,drive=vd1 -drive if=none,format=raw,file="${SEED_IMAGE}",id=vd1,readonly=on \
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0 \
-nographic \
-serial mon:stdio </dev/null >"${QEMU_LOG}" 2>&1

rm -f "${SEED_IMAGE}"
rm -f "${USER_DATA_RENDERED}"

# cloud-init reports failures on stdout but still powers the machine off, so the
# exit code of QEMU alone is not a sufficient success criterion.
if ! grep -q "score ITF image customization finished" "${QEMU_LOG}"; then
echo "Error: cloud-init did not complete the image customization." >&2
echo "----- QEMU log -----" >&2
cat "${QEMU_LOG}" >&2
exit 1
fi
63 changes: 63 additions & 0 deletions scripts/render_user_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

"""Render cloud-init user-data using network values from qemu_config.json."""

from __future__ import annotations

import ipaddress
import json
import sys


def main() -> None:
if len(sys.argv) != 4:
raise SystemExit("Error: Expected 3 arguments (qemu config, user-data template, rendered user-data)")

qemu_config_path = sys.argv[1]
user_data_template_path = sys.argv[2]
user_data_rendered_path = sys.argv[3]

with open(qemu_config_path, encoding="utf-8") as f:
qemu_config = json.load(f)

networks = qemu_config.get("networks")
if not isinstance(networks, list) or not networks:
raise SystemExit("Error: qemu_config.json must provide at least one network")

network = networks[0]
for key in ("ip_address", "gateway"):
if key not in network:
raise SystemExit(f"Error: missing '{key}' in first network entry")

ip_address = str(network["ip_address"])
gateway = str(network["gateway"])
ipaddress.ip_address(ip_address)
ipaddress.ip_address(gateway)

with open(user_data_template_path, encoding="utf-8") as f:
user_data = f.read()

if "__SCORE_ITF_IP_ADDRESS__" not in user_data or "__SCORE_ITF_GATEWAY__" not in user_data:
raise SystemExit("Error: user-data template placeholders are missing")

user_data = user_data.replace("__SCORE_ITF_IP_ADDRESS__", ip_address)
user_data = user_data.replace("__SCORE_ITF_GATEWAY__", gateway)

with open(user_data_rendered_path, "w", encoding="utf-8") as f:
f.write(user_data)


if __name__ == "__main__":
main()
24 changes: 23 additions & 1 deletion test/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ py_itf_test(
py_itf_test(
name = "test_ebclfsa_ping",
srcs = [
"test_ebclfsa_ping.py",
"test_ping.py",
],
args = [
"--qemu-config=$(location //test/resources/ebclfsa_aarch64:qemu_config)",
Expand All @@ -233,13 +233,35 @@ py_itf_test(
],
)

py_itf_test(
name = "test_ubuntu_ping",
srcs = [
"test_ping.py",
],
args = [
"--qemu-config=$(location //test/resources/ubuntu_x86_64:qemu_config)",
"--qemu-rootfs=$(location //test/resources/ubuntu_x86_64:image)",
],
data = [
"//test/resources/ubuntu_x86_64:image",
"//test/resources/ubuntu_x86_64:qemu_config",
],
plugins = [
"//score/itf/plugins:qemu_plugin",
],
tags = [
"manual",
],
)

test_suite(
name = "linux_qemu_tests",
tags = [
"manual",
],
tests = [
":test_ebclfsa_ping",
":test_ubuntu_ping",
],
)

Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions test/resources/ebclfsa_aarch64/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ genrule(
srcs = [
":extract-fastdev-image",
":network_config_tar",
"build_image.sh",
"//scripts:build_ebclfsa_aarch64_image.sh",
],
outs = [
"ebcl-qemuarm64-itf/fastdev-ubuntu-ebclfsa-ebcl-qemuarm64.wic",
"ebcl-qemuarm64-itf/fastdev-ubuntu-ebclfsa-ebcl-qemuarm64-vmlinux",
],
cmd = """
bash $(location build_image.sh) \
bash $(location //scripts:build_ebclfsa_aarch64_image.sh) \
$(RULEDIR) \
$(RULEDIR)/ebcl-qemuarm64 \
$(RULEDIR)/ebcl-qemuarm64-itf \
Expand Down
Loading
Loading