diff --git a/.bazelrc b/.bazelrc index 6376ac2a..8456ee2a 100644 --- a/.bazelrc +++ b/.bazelrc @@ -17,6 +17,7 @@ test:qemu-integration --config=x86_64-qnx test:qemu-integration --run_under=//scripts:run_under_qemu test:qemu-integration --build_tests_only +build:qemu-integration-linux --extra_toolchains=@score_linux_x86_64_ext4_toolchain//:ext4-x86_64-linux test:qemu-integration-linux --run_under=//scripts:run_under_qemu test:qemu-integration-linux --build_tests_only diff --git a/MODULE.bazel b/MODULE.bazel index 67ff2d20..5991833c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -208,7 +208,7 @@ use_repo( "score_qcc_x86_64_toolchain_pkg", ) -bazel_dep(name = "score_rules_imagefs", version = "0.0.3", dev_dependency = True) +bazel_dep(name = "score_rules_imagefs", version = "0.1.0", dev_dependency = True) imagefs = use_extension("@score_rules_imagefs//extensions:imagefs.bzl", "imagefs", dev_dependency = True) imagefs.toolchain( @@ -219,7 +219,13 @@ imagefs.toolchain( target_os = "qnx", type = "ifs", ) -use_repo(imagefs, "score_qnx_x86_64_ifs_toolchain") +imagefs.toolchain( + name = "score_linux_x86_64_ext4_toolchain", + target_cpu = "x86_64", + target_os = "linux", + type = "ext4", +) +use_repo(imagefs, "score_linux_x86_64_ext4_toolchain", "score_qnx_x86_64_ifs_toolchain") ############################################################################### # diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 0240cd3d..76100ee0 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -73,7 +73,8 @@ "https://bcr.bazel.build/modules/bazel_lib/3.0.0-beta.1/MODULE.bazel": "407729e232f611c3270005b016b437005daa7b1505826798ea584169a476e878", "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", - "https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751", + "https://bcr.bazel.build/modules/bazel_lib/3.7.1/MODULE.bazel": "b6fd9b2f8fab956420c11836f416efac4a70e20804ae384ebe62773a4ed70046", + "https://bcr.bazel.build/modules/bazel_lib/3.7.1/source.json": "635fdaa28b50c04febc5e60ef51bc913d3bc87bfbaac7045449273c2341648cb", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -354,6 +355,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_lib/3.0.0-beta.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_lib/3.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_lib/3.7.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.0.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.1.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.2.0/MODULE.bazel": "not found", @@ -515,7 +517,8 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_cpp_toolchains/0.5.1/source.json": "e3bb1beaf9e8e08fe682a616647d17badb1d7f8eeed4d2582277a9d7a1186c62", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/0.1.1/MODULE.bazel": "236e5bdff6f2d6de6f96cc4f5f4b1bf2cd6137547ce279668a2dd4c54cd4236c", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/0.1.2/MODULE.bazel": "d1889bf36241521c5d5c401aaf1e98242a60f9cab9d223f8190e5d7087956e83", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/0.1.2/source.json": "549c18a968c1ab76135ad38fd15981fcc04d377a62f20e2e5e0b5faa9de2b01d", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/1.1.0/MODULE.bazel": "e5c656abcad121a89cbabad3550bb20167a6865de219ef6e1c68ca30567e1c32", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/1.1.0/source.json": "9797d56e9dd68e9d3e8b674402df2d6d5e2174f84184ca4cbee78fc07dc117d9", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_cr_checker/0.2.2/MODULE.bazel": "dc36d9c35543db918c3fb5b93a8e684431f56c7c784cf2a1b90f35802a373c98", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_cr_checker/0.3.1/MODULE.bazel": "f49e037d7fbc0b2a8b2734fc6b47334e8cc8589ca7a5aa0f3ccca85cc5f79fac", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_dash_license_checker/0.1.1/MODULE.bazel": "76681dbd2d45b5c540869a2337174086c56c54953aab1d02cd878b59d31d13a5", @@ -556,8 +559,8 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.2/MODULE.bazel": "f25490f64035a0e3a0d53ad9cb6164e8325ce6cf2a7ee68c6ae153840cb2497e", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.4/MODULE.bazel": "53bd16dfbb1fb8ecf6822fb26f9f4e8333bac7b14d12bb02bf84078063820a31", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.4/source.json": "28ed0207a16f8498a84fae4983fa73060805003a4fbd68c6d749e2f1cc14601f", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rules_imagefs/0.0.3/MODULE.bazel": "d87fcd3485438284fa96d6a5a81923d6b6672468947423517cc43c4d7692ca6a", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rules_imagefs/0.0.3/source.json": "5f2de07264fbc8f3adecffde5aea326b9f8fccf9e74fd6a057fc9c3556b00cfa", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rules_imagefs/0.1.0/MODULE.bazel": "7964f78b6a92aebc38916dedbfb5f011d4fa4c385d86f7612c39c7a423b012cc", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rules_imagefs/0.1.0/source.json": "8aa1214aec6645a9cbbfec86b855402de2f0c42b9aa37224279a64788350a58e", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rust_policies/0.0.2/MODULE.bazel": "ade2bad4a331b02d9b7e7d9842e8de8c6fded6186486e02c4f7db5cd4b71d34d", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rust_policies/0.0.2/source.json": "fbcbc738e652b0c68d5d28dd1db09f2e643dc111f5739b2f6af7ec56c2e88043", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_starpls_lsp/0.1.0/MODULE.bazel": "b2f8c4c8d8e851706255ff9002b448bff6e040b8f0c6adedbde2a09375aa16cc", @@ -2577,20 +2580,80 @@ }, "@@score_rules_imagefs+//extensions:imagefs.bzl%imagefs": { "general": { - "bzlTransitiveDigest": "6ZLANe53LXwcnne1ZrZm9GkJ9H5VdsCLSgSNHyUrqA4=", - "usagesDigest": "v6LxuRqCAQvitgq4q5lQseaSUnV05WHiDU49y4X6Ohc=", + "bzlTransitiveDigest": "AhldzzceHQweCskTyocFKGDFKZfW2t5IdpfsFJV4S9g=", + "usagesDigest": "rvePbK74MbNcQLgmiJ6PcIOcLxli5RcnYZlpoE7btnE=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { "score_qnx_x86_64_ifs_toolchain": { - "repoRuleId": "@@score_rules_imagefs+//rules/qnx:imagefs_toolchain.bzl%imagefs_toolchain", + "repoRuleId": "@@score_rules_imagefs+//rules:imagefs_toolchain.bzl%imagefs_toolchain", "attributes": { "tc_cpu": "x86_64", "tc_os": "qnx", "tc_pkg_repo": "'@@score_bazel_cpp_toolchains++gcc+score_qcc_x86_64_toolchain_pkg'", "sdp_version": "8.0.0", - "tc_type": "ifs" + "tc_type": "ifs", + "coreutils_pkg_repo": "" + } + }, + "score_linux_x86_64_ext4_toolchain_coreutils_darwin_amd64": { + "repoRuleId": "@@bazel_lib+//lib/private:coreutils_toolchain.bzl%coreutils_platform_repo", + "attributes": { + "platform": "darwin_amd64", + "version": "0.5.0" + } + }, + "score_linux_x86_64_ext4_toolchain_coreutils_darwin_arm64": { + "repoRuleId": "@@bazel_lib+//lib/private:coreutils_toolchain.bzl%coreutils_platform_repo", + "attributes": { + "platform": "darwin_arm64", + "version": "0.5.0" + } + }, + "score_linux_x86_64_ext4_toolchain_coreutils_linux_amd64": { + "repoRuleId": "@@bazel_lib+//lib/private:coreutils_toolchain.bzl%coreutils_platform_repo", + "attributes": { + "platform": "linux_amd64", + "version": "0.5.0" + } + }, + "score_linux_x86_64_ext4_toolchain_coreutils_linux_arm64": { + "repoRuleId": "@@bazel_lib+//lib/private:coreutils_toolchain.bzl%coreutils_platform_repo", + "attributes": { + "platform": "linux_arm64", + "version": "0.5.0" + } + }, + "score_linux_x86_64_ext4_toolchain_coreutils_windows_amd64": { + "repoRuleId": "@@bazel_lib+//lib/private:coreutils_toolchain.bzl%coreutils_platform_repo", + "attributes": { + "platform": "windows_amd64", + "version": "0.5.0" + } + }, + "score_linux_x86_64_ext4_toolchain_coreutils_windows_arm64": { + "repoRuleId": "@@bazel_lib+//lib/private:coreutils_toolchain.bzl%coreutils_platform_repo", + "attributes": { + "platform": "windows_arm64", + "version": "0.5.0" + } + }, + "score_linux_x86_64_ext4_toolchain_coreutils_toolchains": { + "repoRuleId": "@@bazel_lib+//lib/private:coreutils_toolchain.bzl%coreutils_toolchains_repo", + "attributes": { + "user_repository_name": "score_linux_x86_64_ext4_toolchain_coreutils" + } + }, + "score_linux_x86_64_ext4_toolchain": { + "repoRuleId": "@@score_rules_imagefs+//rules:imagefs_toolchain.bzl%imagefs_toolchain", + "attributes": { + "tc_cpu": "x86_64", + "tc_os": "linux", + "tc_pkg_repo": "", + "sdp_version": "8.0.0", + "tc_type": "ext4", + "coreutils_pkg_repo": "@score_linux_x86_64_ext4_toolchain_coreutils_linux_amd64" } } }, @@ -2600,6 +2663,16 @@ "score_qcc_x86_64_toolchain_pkg", "score_bazel_cpp_toolchains++gcc+score_qcc_x86_64_toolchain_pkg" ], + [ + "bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "score_rules_imagefs+", + "bazel_lib", + "bazel_lib+" + ], [ "score_rules_imagefs+", "bazel_tools", diff --git a/README.md b/README.md index 12eb6760..e5fd7c9f 100644 --- a/README.md +++ b/README.md @@ -285,10 +285,13 @@ py_itf_test( args = [ "--qemu-rootfs=$(location @os_images//ubuntu_x86_64:image)", "--qemu-config=$(location @os_images//ubuntu_x86_64:qemu_config)", + # optional disk image with test data + "--qemu-disk=$(location @os_images//ubuntu_x86_64:qemu_disk_image)", ], data = [ "@os_images//ubuntu_x86_64:image", "@os_images//ubuntu_x86_64:qemu_config", + "@os_images//ubuntu_x86_64:qemu_disk_image", ], plugins = ["@score_itf//score/itf/plugins:qemu_plugin"], ) @@ -304,11 +307,14 @@ py_itf_test( "--qemu-rootfs=$(location @os_images//ebclfsa_aarch64:image)", "--qemu-kernel=$(location @os_images//ebclfsa_aarch64:kernel)", "--qemu-config=$(location @os_images//ebclfsa_aarch64:qemu_config)", + # optional disk image with test data + "--qemu-disk=$(location @os_images//ebclfsa_aarch64:qemu_disk_image)", ], data = [ "@os_images//ebclfsa_aarch64:image", "@os_images//ebclfsa_aarch64:kernel", "@os_images//ebclfsa_aarch64:qemu_config", + "@os_images//ebclfsa_aarch64:qemu_disk_image", ], plugins = ["@score_itf//score/itf/plugins:qemu_plugin"], ) diff --git a/score/itf/plugins/qemu/__init__.py b/score/itf/plugins/qemu/__init__.py index 071b2a14..47696f6a 100644 --- a/score/itf/plugins/qemu/__init__.py +++ b/score/itf/plugins/qemu/__init__.py @@ -80,6 +80,13 @@ def pytest_addoption(parser): help="Path to a QEMU disk image (qcow2, wic, or img). " "An ephemeral overlay is created so the original image is not modified.", ) + parser.addoption( + "--qemu-disk", + action="store", + default=None, + help="Path to an additional disk image to attach to the target as a second block device. " + "A qcow2 overlay is created so the original image is not modified.", + ) @pytest.fixture(scope="session") @@ -96,6 +103,7 @@ def config(request): qemu_kernel = request.config.getoption("qemu_kernel") qemu_image = request.config.getoption("qemu_image") rootfs = request.config.getoption("qemu_rootfs") + disk = request.config.getoption("qemu_disk") if qemu_image: logger.warning( @@ -108,6 +116,7 @@ def config(request): qemu_config=load_configuration(qemu_config), qemu_kernel=qemu_kernel, qemu_rootfs=rootfs, + qemu_disk=disk, ) @@ -115,14 +124,18 @@ def config(request): def target_init(config, request, dlt): logger.info(f"Starting tests on host: {socket.gethostname()}") overlay_path = None - if config.qemu_rootfs: - overlay_path = _create_overlay(os.path.abspath(config.qemu_rootfs)) + disk_overlay_path = None try: + if config.qemu_rootfs: + overlay_path = _create_overlay(os.path.abspath(config.qemu_rootfs)) + if config.qemu_disk: + disk_overlay_path = _create_overlay(os.path.abspath(config.qemu_disk)) with qemu_target( Bunch( qemu_config=config.qemu_config, qemu_kernel=config.qemu_kernel, qemu_rootfs=overlay_path, + qemu_disk=disk_overlay_path, ) ) as qemu: pre_tests_phase(qemu) @@ -130,3 +143,5 @@ def target_init(config, request, dlt): finally: if overlay_path and os.path.exists(overlay_path): os.unlink(overlay_path) + if disk_overlay_path and os.path.exists(disk_overlay_path): + os.unlink(disk_overlay_path) diff --git a/score/itf/plugins/qemu/qemu.py b/score/itf/plugins/qemu/qemu.py index 4f06905c..49cbc75b 100644 --- a/score/itf/plugins/qemu/qemu.py +++ b/score/itf/plugins/qemu/qemu.py @@ -25,6 +25,9 @@ "network_device": "virtio-net-pci", "machine": "pc", "block_device": "virtio-blk-pci", + # virtio-blk-pci is probed by the guest in the same order the devices are + # specified on the command line. + "block_device_order": "ascending", }, "virt-aarch64": { "architecture": "aarch64", @@ -32,6 +35,9 @@ "network_device": "virtio-net-device", "machine": "virt,virtualization=true,gic-version=3", "block_device": "virtio-blk-device", + # virtio-blk-device (virtio-mmio) is probed by the guest in the reverse order + # the devices are specified on the command line. + "block_device_order": "descending", }, } @@ -55,6 +61,7 @@ def __init__( port_forwarding, rootfs, kernel_cmdline, + disk, ): """Create a QEMU instance with the specified parameters. @@ -67,6 +74,7 @@ def __init__( :param list port_forwarding: List of port forwarding configurations. :param str rootfs: Optional path to a qcow2 disk image. :param str kernel_cmdline: Optional kernel command line string. + :param str disk: Optional path to an additional qcow2 disk image. """ if machine not in _SUPPORTED_MACHINES: raise ValueError("machine must be one of: " + ", ".join(sorted(_SUPPORTED_MACHINES))) @@ -78,6 +86,7 @@ def __init__( self.__port_forwarding = port_forwarding self.__rootfs = rootfs self.__kernel_cmdline = kernel_cmdline + self.__disk = disk self.__check_qemu_is_installed() @@ -147,10 +156,10 @@ def __build_qemu_command(self): + self.__network_devices_args() + self.__port_forwarding_args() + self.__kernel_args() - + self.__rootfs_args() + + self.__disks_args() ) - def __kernel_args(self): + def __kernel_args(self) -> list[str]: if not self.__path_to_kernel_image: return [] args = ["-kernel", self.__path_to_kernel_image] @@ -158,14 +167,25 @@ def __kernel_args(self): args.extend(["-append", self.__kernel_cmdline]) return args - def __rootfs_args(self): - if not self.__rootfs: + def __disks_args(self) -> list[str]: + # Order the disks so that, regardless of the guest's probing order, the rootfs + # always ends up as the first block device (/dev/vda) in the guest. + disks = [self.__rootfs, self.__disk] + if self.__arch_config["block_device_order"] == "descending": + disks = list(reversed(disks)) + args = [] + for id, disk in enumerate(disks): + args += self.__disk_args(disk, id) + return args + + def __disk_args(self, disk: str, id: int) -> list[str]: + if not disk: return [] return [ "-device", - f"{self.__arch_config['block_device']},drive=vd0", + f"{self.__arch_config['block_device']},drive=vd{id}", "-drive", - f"if=none,format=qcow2,file={self.__rootfs},id=vd0", + f"if=none,format=qcow2,file={disk},id=vd{id}", ] def __network_devices_args(self): diff --git a/score/itf/plugins/qemu/qemu_process.py b/score/itf/plugins/qemu/qemu_process.py index 7f1221a0..765e0379 100644 --- a/score/itf/plugins/qemu/qemu_process.py +++ b/score/itf/plugins/qemu/qemu_process.py @@ -30,6 +30,7 @@ def __init__( machine, rootfs, kernel_cmdline, + disk, ): self._path_to_qemu_kernel_image = path_to_qemu_kernel_image self._available_ram = available_ram @@ -39,6 +40,7 @@ def __init__( self._machine = machine self._rootfs = rootfs self._kernel_cmdline = kernel_cmdline + self._disk = disk self._qemu = Qemu( self._path_to_qemu_kernel_image, self._available_ram, @@ -48,6 +50,7 @@ def __init__( machine=self._machine, rootfs=self._rootfs, kernel_cmdline=self._kernel_cmdline, + disk=self._disk, ) self._console = None @@ -65,6 +68,8 @@ def start(self): logger.info(f"Using QEMU kernel command line: {self._kernel_cmdline}") if self._rootfs is not None: logger.info(f"Using QEMU root filesystem image: {self._rootfs}") + if self._disk is not None: + logger.info(f"Using QEMU additional disk image: {self._disk}") subprocess_params = { "stdin": subprocess.PIPE, "stdout": subprocess.PIPE, diff --git a/score/itf/plugins/qemu/qemu_target.py b/score/itf/plugins/qemu/qemu_target.py index f5feb55d..43d845d2 100644 --- a/score/itf/plugins/qemu/qemu_target.py +++ b/score/itf/plugins/qemu/qemu_target.py @@ -293,6 +293,7 @@ def qemu_target(test_config): machine=test_config.qemu_config.qemu_machine, rootfs=test_config.qemu_rootfs, kernel_cmdline=test_config.qemu_config.qemu_kernel_cmdline, + disk=test_config.qemu_disk, ) else: process_ctx = nullcontext() diff --git a/test/integration/BUILD b/test/integration/BUILD index 66a190ec..4c9aa2dd 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -211,16 +211,19 @@ py_itf_test( ) py_itf_test( - name = "test_ebclfsa_ping", + name = "test_ebclfsa", srcs = [ "test_ping.py", + "test_qemu_disk.py", ], args = [ "--qemu-config=$(location @os_images//ebclfsa_aarch64:qemu_config)", "--qemu-kernel=$(location @os_images//ebclfsa_aarch64:kernel)", "--qemu-rootfs=$(location @os_images//ebclfsa_aarch64:image)", + "--qemu-disk=$(location //test/resources:qemu_disk_image)", ], data = [ + "//test/resources:qemu_disk_image", "@os_images//ebclfsa_aarch64:image", "@os_images//ebclfsa_aarch64:kernel", "@os_images//ebclfsa_aarch64:qemu_config", @@ -231,18 +234,22 @@ py_itf_test( tags = [ "manual", ], + target_compatible_with = ["@platforms//os:linux"], ) py_itf_test( - name = "test_ubuntu_ping", + name = "test_ubuntu", srcs = [ "test_ping.py", + "test_qemu_disk.py", ], args = [ "--qemu-config=$(location @os_images//ubuntu_x86_64:qemu_config)", "--qemu-rootfs=$(location @os_images//ubuntu_x86_64:image)", + "--qemu-disk=$(location //test/resources:qemu_disk_image)", ], data = [ + "//test/resources:qemu_disk_image", "@os_images//ubuntu_x86_64:image", "@os_images//ubuntu_x86_64:qemu_config", ], @@ -252,6 +259,7 @@ py_itf_test( tags = [ "manual", ], + target_compatible_with = ["@platforms//os:linux"], ) test_suite( @@ -260,8 +268,8 @@ test_suite( "manual", ], tests = [ - ":test_ebclfsa_ping", - ":test_ubuntu_ping", + ":test_ebclfsa", + ":test_ubuntu", ], ) diff --git a/test/integration/test_qemu_disk.py b/test/integration/test_qemu_disk.py new file mode 100644 index 00000000..dc7ed989 --- /dev/null +++ b/test/integration/test_qemu_disk.py @@ -0,0 +1,48 @@ +# ******************************************************************************* +# 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 a second disk, passed to the QEMU plugin via ``--qemu-disk``, +shows up in the guest, can be mounted and contains the expected content. +""" + +EXPECTED_DISK_CONTENT = "Hello from the QEMU disk image!\n" + +# The additional disk is attached as the second virtio block device. The +# rootfs occupies the first one, so the disk always shows up as /dev/vdb. +DISK_DEVICE = "/dev/vdb" +MOUNT_POINT = "/mnt/qemu_disk" + + +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_can_be_mounted_and_has_expected_content(target): + exit_code, _ = target.execute(f"mkdir -p {MOUNT_POINT} && mount {DISK_DEVICE} {MOUNT_POINT}") + assert exit_code == 0, "Mounting the additional disk failed" + try: + exit_code, output = target.execute(f"cat {MOUNT_POINT}/qemu_disk_content.txt") + assert exit_code == 0 + assert output.decode("utf-8") == EXPECTED_DISK_CONTENT + finally: + target.execute(f"umount {MOUNT_POINT}") + + +def test_disk_is_writable(target): + exit_code, _ = target.execute(f"mkdir -p {MOUNT_POINT} && mount {DISK_DEVICE} {MOUNT_POINT}") + assert exit_code == 0, "Mounting the additional disk failed" + try: + exit_code, _ = target.execute(f"touch {MOUNT_POINT}/should_be_writable") + assert exit_code == 0, "Writing to the disk should work" + finally: + target.execute(f"umount {MOUNT_POINT}") diff --git a/test/resources/BUILD b/test/resources/BUILD index f5700827..80942648 100644 --- a/test/resources/BUILD +++ b/test/resources/BUILD @@ -11,7 +11,9 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load") +load("@rules_pkg//pkg:mappings.bzl", "pkg_files") load("@rules_pkg//pkg:tar.bzl", "pkg_tar") +load("@score_rules_imagefs//rules/linux:ext4.bzl", "ext4") filegroup( name = "dlt_config", @@ -99,3 +101,20 @@ oci_load( #"@score_itf_examples//:__subpackages__", ], ) + +pkg_files( + name = "qemu_disk_files", + srcs = ["qemu_disk_content.txt"], + prefix = "", +) + +ext4( + name = "qemu_disk_image", + srcs = [":qemu_disk_files"], + out = "qemu_disk.ext4", + tags = ["manual"], + target_compatible_with = ["@platforms//os:linux"], + visibility = [ + "//test:__subpackages__", + ], +) diff --git a/test/resources/qemu_disk_content.txt b/test/resources/qemu_disk_content.txt new file mode 100644 index 00000000..04606245 --- /dev/null +++ b/test/resources/qemu_disk_content.txt @@ -0,0 +1 @@ +Hello from the QEMU disk image! diff --git a/test/unit/test_qemu.py b/test/unit/test_qemu.py index 9610a832..9cc7747e 100644 --- a/test/unit/test_qemu.py +++ b/test/unit/test_qemu.py @@ -25,6 +25,7 @@ def _build_qemu( path_to_kernel_image=None, kernel_cmdline=None, rootfs=None, + disk=None, network_adapters=None, port_forwarding=None, ): @@ -44,6 +45,7 @@ def _build_qemu( port_forwarding=port_forwarding, rootfs=rootfs, kernel_cmdline=kernel_cmdline, + disk=disk, ) @@ -59,6 +61,7 @@ def test_invalid_machine_is_rejected(mocker): port_forwarding=[], rootfs=None, kernel_cmdline=None, + disk=None, ) @@ -99,7 +102,7 @@ def test_kernel_args_are_empty_without_kernel_image(mocker): def test_rootfs_args_include_arch_specific_block_device(mocker): qemu = _build_qemu(mocker, machine="virt-aarch64", rootfs="/tmp/rootfs.qcow2") - assert qemu._Qemu__rootfs_args() == [ + assert qemu._Qemu__disk_args("/tmp/rootfs.qcow2", 0) == [ "-device", "virtio-blk-device,drive=vd0", "-drive", @@ -110,7 +113,41 @@ def test_rootfs_args_include_arch_specific_block_device(mocker): def test_rootfs_args_are_empty_without_rootfs(mocker): qemu = _build_qemu(mocker) - assert qemu._Qemu__rootfs_args() == [] + assert qemu._Qemu__disk_args(None, 0) == [] + + +def test_disk_args_include_arch_specific_block_device(mocker): + qemu = _build_qemu(mocker, machine="virt-aarch64", disk="/tmp/disk.qcow2") + + assert qemu._Qemu__disk_args("/tmp/disk.qcow2", 1) == [ + "-device", + "virtio-blk-device,drive=vd1", + "-drive", + "if=none,format=qcow2,file=/tmp/disk.qcow2,id=vd1", + ] + + +def test_disk_args_are_empty_without_disk(mocker): + qemu = _build_qemu(mocker) + + assert qemu._Qemu__disk_args(None, 0) == [] + + +def test_rootfs_and_disk_can_be_combined(mocker): + qemu = _build_qemu(mocker, rootfs="/tmp/rootfs.qcow2", disk="/tmp/disk.qcow2") + + assert qemu._Qemu__disk_args("/tmp/rootfs.qcow2", 0) == [ + "-device", + "virtio-blk-pci,drive=vd0", + "-drive", + "if=none,format=qcow2,file=/tmp/rootfs.qcow2,id=vd0", + ] + assert qemu._Qemu__disk_args("/tmp/disk.qcow2", 1) == [ + "-device", + "virtio-blk-pci,drive=vd1", + "-drive", + "if=none,format=qcow2,file=/tmp/disk.qcow2,id=vd1", + ] def test_network_args_skip_loopback_and_use_machine_specific_device(mocker):