Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
14 changes: 13 additions & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,24 @@ bazel_dep(name = "rules_pkg", version = "1.0.1")
bazel_dep(name = "score_itf", version = "0.5.0", dev_dependency = True)
bazel_dep(name = "rules_python", version = "1.8.5", dev_dependency = True)

# TODO: Switch back to a released version once `--qemu-disk` is part of a tagged release.
git_override(
module_name = "score_itf",
commit = "53a7ba37bf304230fa846ec303c902ec12c8aaa2",
commit = "8aa4723dffaff85f4405315f9500a651310afb74",
remote = "https://github.com/eclipse-score/itf.git",
)

# Filesystem image rules, used to build the additional disk image attached to the
# QEMU targets via `--qemu-disk`.
bazel_dep(name = "score_rules_imagefs", version = "0.0.3")

# TODO: Switch back to a released version once the `ext4` rule is available in a tagged release.
git_override(
module_name = "score_rules_imagefs",
commit = "b1278766c01d9f4102c24e87b0ce9d035eaed832",
remote = "https://github.com/eclipse-score/rules_imagefs.git",
)

Comment on lines +33 to +50

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix TODOs

python = use_extension("@rules_python//python/extensions:python.bzl", "python", dev_dependency = True)
python.defaults(python_version = "3.12")
python.toolchain(
Expand Down
22 changes: 22 additions & 0 deletions QEMU_IMAGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ aarch64 image.
The image targets are tagged `manual`, since building them boots QEMU and needs
the base images to be downloaded.

## Additional disk on /opt

Both images mount an additional disk automatically on `/opt`. The ITF QEMU
plugin attaches such a disk when it is passed via `--qemu-disk`; it always shows
up as the second virtio block device `/dev/vdb`. The `fstab` entry uses `nofail`,
so the images keep booting when no additional disk is attached.

`//tests:qemu_disk_image` builds an example ext4 disk with
[rules_imagefs](https://github.com/eclipse-score/rules_imagefs):

```starlark
py_itf_test(
name = "my_test",
args = [
"--qemu-config=$(location //ubuntu_x86_64:qemu_config)",
"--qemu-disk=$(location //tests:qemu_disk_image)",
"--qemu-rootfs=$(location //ubuntu_x86_64:image)",
],
...
)
```

## Build scripts

All the image build scripts are located in the `//scripts` package.
Expand Down
3 changes: 3 additions & 0 deletions scripts/build_ebclfsa_aarch64_image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ done

# Enable root login and empty passwords for integration testing, not for production use.
sshpass -p linux ssh -o StrictHostKeyChecking=no -p 2222 root@localhost "printf 'PermitRootLogin yes\nPermitEmptyPasswords yes\nPasswordAuthentication yes\n' > /etc/ssh/sshd_config.d/99-integration-test.conf"
# Mount an additional disk (ITF "--qemu-disk") on /opt. "nofail" keeps the image
# bootable when no such disk is attached; crinit runs "mount -a" during boot.
sshpass -p linux ssh -o StrictHostKeyChecking=no -p 2222 root@localhost "printf '\n# Additional disk attached by the ITF QEMU plugin\n/dev/vdb\t/opt\tauto\tdefaults,nofail\t0\t0\n' >> /etc/fstab"
sshpass -p linux ssh -o StrictHostKeyChecking=no -p 2222 root@localhost "sed -i 's/^root:[^:]*:/root::/' /etc/shadow && sync && crinit-ctl poweroff" || true

if [[ -f "${WORKING_DIR}/qemu.pid" ]]; then
Expand Down
90 changes: 90 additions & 0 deletions tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,25 @@
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
load("@score_itf//:defs.bzl", "py_itf_test")
load("@score_rules_imagefs//rules/linux:ext4.bzl", "ext4")

pkg_files(
name = "qemu_disk_files",
srcs = ["qemu_disk_content.txt"],
prefix = "",
)

# Additional disk attached to the QEMU targets via `--qemu-disk`. The images
# mount it on /opt automatically, see the `opt-mount` support in each image.
ext4(
name = "qemu_disk_image",
srcs = [":qemu_disk_files"],
out = "qemu_disk.ext4",
target_compatible_with = ["@platforms//os:linux"],
visibility = ["//visibility:public"],
)

py_itf_test(
name = "ubuntu_qemu_ping_test",
Expand Down Expand Up @@ -46,3 +64,75 @@ py_itf_test(
tags = ["qemu-integration"],
target_compatible_with = ["@platforms//os:linux"],
)

py_itf_test(
name = "ubuntu_qemu_opt_mount_test",
srcs = ["test_opt_mount.py"],
args = [
"--qemu-config=$(location //ubuntu_x86_64:qemu_config)",
"--qemu-disk=$(location :qemu_disk_image)",
"--qemu-rootfs=$(location //ubuntu_x86_64:image)",
],
data = [
":qemu_disk_image",
"//ubuntu_x86_64:image",
"//ubuntu_x86_64:qemu_config",
],
plugins = ["@score_itf//score/itf/plugins:qemu_plugin"],
tags = ["qemu-integration"],
target_compatible_with = ["@platforms//os:linux"],
)

py_itf_test(
name = "ubuntu_qemu_no_disk_test",
srcs = ["test_no_disk.py"],
args = [
"--qemu-config=$(location //ubuntu_x86_64:qemu_config)",
"--qemu-rootfs=$(location //ubuntu_x86_64:image)",
],
data = [
"//ubuntu_x86_64:image",
"//ubuntu_x86_64:qemu_config",
],
plugins = ["@score_itf//score/itf/plugins:qemu_plugin"],
tags = ["qemu-integration"],
target_compatible_with = ["@platforms//os:linux"],
)

py_itf_test(
name = "ebclfsa_qemu_opt_mount_test",
srcs = ["test_opt_mount.py"],
args = [
"--qemu-config=$(location //ebclfsa_aarch64:qemu_config)",
"--qemu-disk=$(location :qemu_disk_image)",
"--qemu-kernel=$(location //ebclfsa_aarch64:kernel)",
"--qemu-rootfs=$(location //ebclfsa_aarch64:image)",
],
data = [
":qemu_disk_image",
"//ebclfsa_aarch64:image",
"//ebclfsa_aarch64:kernel",
"//ebclfsa_aarch64:qemu_config",
],
plugins = ["@score_itf//score/itf/plugins:qemu_plugin"],
tags = ["qemu-integration"],
target_compatible_with = ["@platforms//os:linux"],
)

py_itf_test(
name = "ebclfsa_qemu_no_disk_test",
srcs = ["test_no_disk.py"],
args = [
"--qemu-config=$(location //ebclfsa_aarch64:qemu_config)",
"--qemu-kernel=$(location //ebclfsa_aarch64:kernel)",
"--qemu-rootfs=$(location //ebclfsa_aarch64:image)",
],
data = [
"//ebclfsa_aarch64:image",
"//ebclfsa_aarch64:kernel",
"//ebclfsa_aarch64:qemu_config",
],
plugins = ["@score_itf//score/itf/plugins:qemu_plugin"],
tags = ["qemu-integration"],
target_compatible_with = ["@platforms//os:linux"],
)
1 change: 1 addition & 0 deletions tests/qemu_disk_content.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from the QEMU disk image!
36 changes: 36 additions & 0 deletions tests/test_no_disk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# *******************************************************************************
# 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
# *******************************************************************************
"""Verifies that the automatic /opt mount does not break a target that is booted
without an additional disk.
"""

MOUNT_POINT = "/opt"


def test_no_second_block_device(target):
exit_code, _ = target.execute("test -b /dev/vdb")
assert exit_code != 0, "No additional disk was attached, /dev/vdb must not exist"


def test_opt_is_not_a_mount_point(target):
exit_code, _ = target.execute(f"grep -q ' {MOUNT_POINT} ' /proc/mounts")
assert exit_code != 0, (
f"{MOUNT_POINT} must not be mounted without an additional disk"
)


def test_opt_is_usable(target):
exit_code, _ = target.execute(
f"test -d {MOUNT_POINT} && touch {MOUNT_POINT}/should_be_writable"
)
assert exit_code == 0, f"{MOUNT_POINT} should still be a usable directory"
45 changes: 45 additions & 0 deletions tests/test_opt_mount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# *******************************************************************************
# 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
# *******************************************************************************
"""Verifies that an additional disk passed via ``--qemu-disk`` is mounted on
/opt automatically, without any help from the test code.
"""

# The additional disk is always attached as the second virtio block device, the
# root filesystem occupies the first one.
DISK_DEVICE = "/dev/vdb"
MOUNT_POINT = "/opt"
CONTENT_FILE = f"{MOUNT_POINT}/qemu_disk_content.txt"
EXPECTED_CONTENT = "Hello from the QEMU disk image!"


def test_disk_device_is_visible(target):
exit_code, _ = target.execute(f"test -b {DISK_DEVICE}")
assert exit_code == 0, f"Expected block device {DISK_DEVICE} to be present"


def test_disk_is_mounted_on_opt(target):
exit_code, _ = target.execute(
f"grep -q '^{DISK_DEVICE} {MOUNT_POINT} ' /proc/mounts"
)
assert exit_code == 0, f"{DISK_DEVICE} is not mounted on {MOUNT_POINT}"


def test_opt_contains_disk_content(target):
exit_code, output = target.execute(f"cat {CONTENT_FILE}")
assert exit_code == 0, f"{CONTENT_FILE} is not readable"
assert output.decode("utf-8").strip() == EXPECTED_CONTENT


def test_opt_is_writable(target):
exit_code, _ = target.execute(f"touch {MOUNT_POINT}/should_be_writable")
assert exit_code == 0, f"{MOUNT_POINT} should be mounted read-write"
3 changes: 3 additions & 0 deletions ubuntu_x86_64/cloud-init/user-data
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ runcmd:
# Speed up boot: nothing must wait for a network that only comes up once
# QEMU has attached the tap device.
systemctl disable systemd-networkd-wait-online.service
# Mount an additional disk (ITF "--qemu-disk") on /opt. "nofail" keeps the
# image bootable when no such disk is attached.
echo '/dev/vdb /opt auto defaults,nofail,x-systemd.device-timeout=5s 0 0' >> /etc/fstab
# From now on the image is fully configured; never run cloud-init again.
touch /etc/cloud/cloud-init.disabled
sync
Expand Down
Loading