diff --git a/.bazelrc b/.bazelrc index 3af920b..4350a90 100644 --- a/.bazelrc +++ b/.bazelrc @@ -15,6 +15,7 @@ build:x86_64-qnx --credential_helper=*.qnx.com=%workspace%/tools/qnx_credential_ test:qemu-integration --config=x86_64-qnx test:qemu-integration --run_under=//scripts:run_under_qemu +test:qemu-integration --build_tests_only build --java_language_version=17 build --java_runtime_version=remotejdk_17 diff --git a/.github/workflows/itf.yml b/.github/workflows/itf.yml index 81ef349..7b82465 100644 --- a/.github/workflows/itf.yml +++ b/.github/workflows/itf.yml @@ -25,6 +25,18 @@ jobs: uses: docker/setup-buildx-action@v3.8.0 - name: Setup Bazel uses: bazel-contrib/setup-bazel@0.14.0 + - name: Install qemu + run: | + sudo apt-get update + sudo apt-get install -y qemu-system + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: Allow unprivileged user namespaces + run: | + sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0 - name: Run build run: | bazel build //... diff --git a/MODULE.bazel b/MODULE.bazel index e6c0621..6f3a03f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -224,3 +224,17 @@ imagefs.toolchain( type = "ifs", ) use_repo(imagefs, "score_qnx_x86_64_ifs_toolchain") + +############################################################################### +# +# EBclfsa fastdev archive repository for Linux QEMU integration tests +# +############################################################################### +http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") + +http_file( + name = "eb_corbos_toolkit_fastdev_archive", + downloaded_file_path = "fastdev-ubuntu-ebclfsa-ebcl-qemuarm64.tar.gz", + sha256 = "9ca3891b27e4b7bbf6c519d8924283e89c03b62513a988f751a3ee3d10c293e4", + urls = ["https://github.com/elektrobit/eb_corbos_toolkit/releases/download/v2.0.0-beta1/fastdev-ubuntu-ebclfsa-ebcl-qemuarm64.tar.gz"], +) diff --git a/score/itf/plugins/qemu/__init__.py b/score/itf/plugins/qemu/__init__.py index 928b335..168cc37 100644 --- a/score/itf/plugins/qemu/__init__.py +++ b/score/itf/plugins/qemu/__init__.py @@ -11,7 +11,10 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* import logging +import os +import subprocess import socket +import tempfile import pytest from score.itf.plugins.qemu.qemu_target import qemu_target @@ -23,6 +26,41 @@ logger = logging.getLogger(__name__) +def _get_image_format(path_to_image: str) -> str: + """Determine the disk image format based on the file extension.""" + image_lower = path_to_image.lower() + if image_lower.endswith(".wic") or image_lower.endswith(".img"): + return "raw" + return "qcow2" + + +def _create_overlay(base_image: str) -> str: + """Create a temporary qcow2 overlay backed by *base_image*. + + All writes go to the ephemeral overlay so the original image is never + modified. The caller is responsible for cleaning up the returned path. + """ + overlay_fd, overlay_path = tempfile.mkstemp(suffix=".qcow2", prefix="qemu_overlay_") + os.close(overlay_fd) + subprocess.run( + [ + "qemu-img", + "create", + "-f", + "qcow2", + "-b", + base_image, + "-F", + _get_image_format(base_image), + overlay_path, + ], + check=True, + capture_output=True, + ) + logger.info(f"Created qcow2 overlay: {overlay_path} (backing: {base_image})") + return overlay_path + + def pytest_addoption(parser): parser.addoption( "--qemu-config", @@ -30,7 +68,32 @@ def pytest_addoption(parser): required=True, help="Path to json file with target configurations.", ) - parser.addoption("--qemu-image", action="store", help="Path to a QEMU image") + parser.addoption( + "--qemu-image", + action="store", + default=None, + help="Path to a QEMU kernel image", + ) + parser.addoption( + "--qemu-disk-image", + action="store", + default=None, + 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-architecture", + action="store", + choices=("x86_64", "aarch64"), + default="x86_64", + help="Target CPU architecture used to select the QEMU binary and CPU model.", + ) + parser.addoption( + "--qemu-kernel-cmdline-file", + action="store", + default=None, + help="Path to a file containing the Linux kernel command line.", + ) @pytest.fixture(scope="session") @@ -43,15 +106,39 @@ def dlt(): @pytest.fixture(scope="session") def config(request): + qemu_kernel_cmdline = None + kernel_cmdline_file = request.config.getoption("qemu_kernel_cmdline_file") + if kernel_cmdline_file: + with open(os.path.abspath(kernel_cmdline_file), encoding="utf-8") as handle: + qemu_kernel_cmdline = handle.read().strip() + return Bunch( qemu_config=load_configuration(request.config.getoption("qemu_config")), qemu_image=request.config.getoption("qemu_image"), + qemu_disk_image=request.config.getoption("qemu_disk_image"), + qemu_architecture=request.config.getoption("qemu_architecture"), + qemu_kernel_cmdline=qemu_kernel_cmdline, ) @pytest.fixture(scope="session") def target_init(config, request, dlt): logger.info(f"Starting tests on host: {socket.gethostname()}") - with qemu_target(config) as qemu: - pre_tests_phase(qemu) - yield qemu + overlay_path = None + if config.qemu_disk_image: + overlay_path = _create_overlay(os.path.abspath(config.qemu_disk_image)) + try: + with qemu_target( + Bunch( + qemu_config=config.qemu_config, + qemu_image=config.qemu_image, + qemu_disk_image=overlay_path, + qemu_architecture=config.qemu_architecture, + qemu_kernel_cmdline=config.qemu_kernel_cmdline, + ) + ) as qemu: + pre_tests_phase(qemu) + yield qemu + finally: + if overlay_path and os.path.exists(overlay_path): + os.unlink(overlay_path) diff --git a/score/itf/plugins/qemu/qemu.py b/score/itf/plugins/qemu/qemu.py index 07fcbeb..8c54698 100644 --- a/score/itf/plugins/qemu/qemu.py +++ b/score/itf/plugins/qemu/qemu.py @@ -10,15 +10,32 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +import logging import os -import shlex import subprocess import sys -import logging logger = logging.getLogger(__name__) +_SUPPORTED_ARCHITECTURES = { + "x86_64": { + "qemu_path": "/usr/bin/qemu-system-x86_64", + "cpu": "Cascadelake-Server-v5", + "network_device": "virtio-net-pci", + "machine": "pc", + "block_device": "virtio-blk-pci", + }, + "aarch64": { + "qemu_path": "/usr/bin/qemu-system-aarch64", + "cpu": "cortex-a53", + "network_device": "virtio-net-device", + "machine": "virt,virtualization=true,gic-version=3", + "block_device": "virtio-blk-device", + }, +} + + class Qemu: """ This class shall be used to start an qemu instance based on pre-configured Qemu parameters. @@ -27,32 +44,37 @@ class Qemu: def __init__( self, path_to_image, - ram="1G", - cores="2", - cpu="Cascadelake-Server-v5", - network_adapters=[], - port_forwarding=[], + ram, + cores, + architecture, + network_adapters, + port_forwarding, + disk_image, + kernel_cmdline, ): """Create a QEMU instance with the specified parameters. :param str path_to_image: The path to the Qemu image file. :param str ram: The amount of RAM to allocate to the QEMU instance. :param str cores: The number of CPU cores to allocate to the QEMU instance. - :param str cpu: The CPU model to emulate. - Default is Cascadelake-Server-v5 used to emulate modern Intel CPU features. - For older Ubuntu versions change that to host in case of errors. + :param str architecture: The CPU architecture to emulate (x86_64 or aarch64). + :param list network_adapters: List of network adapter names. + :param list port_forwarding: List of port forwarding configurations. + :param str disk_image: Optional path to a qcow2 disk image. + :param str kernel_cmdline: Optional kernel command line string. """ - self.__qemu_path = "/usr/bin/qemu-system-x86_64" + if architecture not in _SUPPORTED_ARCHITECTURES: + raise ValueError("architecture must be one of: " + ", ".join(sorted(_SUPPORTED_ARCHITECTURES))) + self.__arch_config = _SUPPORTED_ARCHITECTURES[architecture] self.__path_to_image = path_to_image self.__ram = ram self.__cores = cores - self.__cpu = cpu self.__network_adapters = network_adapters self.__port_forwarding = port_forwarding + self.__disk_image = disk_image + self.__kernel_cmdline = kernel_cmdline self.__check_qemu_is_installed() - self.__find_available_kvm_support() - self.__check_kvm_readable_when_necessary() self._subprocess = None @@ -82,46 +104,28 @@ def stop(self): raise Exception(f"QEMU process returned: {ret}") def __check_qemu_is_installed(self): - if not os.path.isfile(self.__qemu_path): - logger.fatal(f"Qemu is not installed under {self.__qemu_path}") + qemu_path = self.__arch_config["qemu_path"] + if not os.path.isfile(qemu_path): + logger.fatal(f"Qemu is not installed under {qemu_path}") sys.exit(-1) - def __find_available_kvm_support(self): - self._accelerator_support = "kvm" - with open("/proc/cpuinfo") as cpuinfo: - cpu_options = str(cpuinfo.read()) - if "vmx" not in cpu_options and "svm" not in cpu_options: - logger.error("No virtual capability on machine. We're using standard TCG accel on QEMU") - self._accelerator_support = "tcg" - - if not os.path.exists("/dev/kvm"): - logger.error("No KVM available. We're using standard TCG accel on QEMU") - self._accelerator_support = "tcg" - - def __check_kvm_readable_when_necessary(self): - if self._accelerator_support == "kvm": - if not os.access("/dev/kvm", os.R_OK): - logger.fatal( - "You dont have access rights to /dev/kvm. Consider adding yourself to kvm group. Aborting." - ) - sys.exit(-1) - def __build_qemu_command(self): - # Use hardware virtualization if available - accel = ["-enable-kvm"] if self._accelerator_support == "kvm" else ["-accel", "tcg"] - return ( - [f"{self.__qemu_path}"] - + accel - + [ + [ + self.__arch_config["qemu_path"], + # Use hardware virtualization if available + "-accel", + "kvm", + "-accel", + "tcg", "-smp", f"{self.__cores},maxcpus={self.__cores},cores={self.__cores}", + "-machine", + self.__arch_config["machine"], "-cpu", - f"{self.__cpu}", # Specify CPU to emulate + self.__arch_config["cpu"], # Specify CPU to emulate "-m", f"{self.__ram}", # Specify RAM size - "-kernel", - f"{self.__path_to_image}", # Specify kernel image "-nographic", # Disable graphical display (console-only) "-serial", "mon:stdio", # Redirect serial output to console @@ -132,15 +136,35 @@ def __build_qemu_command(self): ] + self.__network_devices_args() + self.__port_forwarding_args() + + self.__kernel_args() + + self.__disk_image_args() ) + def __kernel_args(self): + if not self.__path_to_image: + return [] + args = ["-kernel", self.__path_to_image] + if self.__kernel_cmdline: + args.extend(["-append", self.__kernel_cmdline]) + return args + + def __disk_image_args(self): + if not self.__disk_image: + return [] + return [ + "-device", + f"{self.__arch_config['block_device']},drive=vd0", + "-drive", + f"if=none,format=qcow2,file={self.__disk_image},id=vd0", + ] + def __network_devices_args(self): def get_netdev_args(adapter, id): return [ "-netdev", f"tap,id=t{id},ifname={adapter},script=no,downscript=no", "-device", - f"virtio-net-pci,netdev=t{id},id=nic{id},guest_csum=off", + f"{self.__arch_config['network_device']},netdev=t{id},id=nic{id},guest_csum=off", ] result = [] @@ -157,7 +181,7 @@ def __port_forwarding_args(self): "-netdev", f"user,id=net{id},hostfwd=tcp::{forwarding.host_port}-:{forwarding.guest_port}", "-device", - f"virtio-net-pci,netdev=net{id}", + f"{self.__arch_config['network_device']},netdev=net{id}", ] ) return result diff --git a/score/itf/plugins/qemu/qemu_process.py b/score/itf/plugins/qemu/qemu_process.py index 64e6794..03a721f 100644 --- a/score/itf/plugins/qemu/qemu_process.py +++ b/score/itf/plugins/qemu/qemu_process.py @@ -20,18 +20,34 @@ class QemuProcess: - def __init__(self, path_to_qemu_image, available_ram, available_cores, network_adapters=[], port_forwarding=[]): + def __init__( + self, + path_to_qemu_image, + available_ram, + available_cores, + network_adapters, + port_forwarding, + architecture, + disk_image, + kernel_cmdline, + ): self._path_to_qemu_image = path_to_qemu_image self._available_ram = available_ram self._available_cores = available_cores self._network_adapters = network_adapters self._port_forwarding = port_forwarding + self._architecture = architecture + self._disk_image = disk_image + self._kernel_cmdline = kernel_cmdline self._qemu = Qemu( self._path_to_qemu_image, self._available_ram, self._available_cores, network_adapters=self._network_adapters, port_forwarding=self._port_forwarding, + architecture=self._architecture, + disk_image=self._disk_image, + kernel_cmdline=self._kernel_cmdline, ) self._console = None diff --git a/score/itf/plugins/qemu/qemu_target.py b/score/itf/plugins/qemu/qemu_target.py index a202323..f7180d3 100644 --- a/score/itf/plugins/qemu/qemu_target.py +++ b/score/itf/plugins/qemu/qemu_target.py @@ -283,8 +283,8 @@ def ping_lost(self, timeout, interval=1, wait_ms_precision=None): @contextmanager def qemu_target(test_config): """Context manager for QEMU target setup.""" - with ( - QemuProcess( + if test_config.qemu_image or test_config.qemu_disk_image: + process_ctx = QemuProcess( test_config.qemu_image, test_config.qemu_config.qemu_ram_size, test_config.qemu_config.qemu_num_cores, @@ -292,9 +292,13 @@ def qemu_target(test_config): port_forwarding=test_config.qemu_config.port_forwarding if hasattr(test_config.qemu_config, "port_forwarding") else [], + architecture=test_config.qemu_architecture, + disk_image=test_config.qemu_disk_image, + kernel_cmdline=test_config.qemu_kernel_cmdline, ) - if test_config.qemu_image - else nullcontext() as qemu_process - ): + else: + process_ctx = nullcontext() + + with process_ctx as qemu_process: target = QemuTarget(qemu_process, test_config.qemu_config) yield target diff --git a/test/integration/BUILD b/test/integration/BUILD index 00b8131..ffec33e 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -51,6 +51,7 @@ py_itf_test( plugins = [ "//score/itf/plugins:docker_plugin", ], + target_compatible_with = ["@platforms//os:linux"], ) py_itf_test( @@ -67,6 +68,7 @@ py_itf_test( "//score/itf/plugins:dlt_plugin", "//score/itf/plugins:docker_plugin", ], + target_compatible_with = ["@platforms//os:linux"], ) py_itf_test( @@ -83,6 +85,7 @@ py_itf_test( "//score/itf/plugins:dlt_plugin", "//score/itf/plugins:docker_plugin", ], + target_compatible_with = ["@platforms//os:linux"], ) py_itf_test( @@ -97,6 +100,7 @@ py_itf_test( "//score/itf/plugins:docker_plugin", ], pytest_config = "//:pyproject.toml", # Optional same content as default pytest.ini + target_compatible_with = ["@platforms//os:linux"], ) py_itf_test( @@ -206,12 +210,39 @@ py_itf_test( ], ) +py_itf_test( + name = "test_ebclfsa_ping", + srcs = [ + "test_ebclfsa_ping.py", + ], + args = [ + "--qemu-config=$(location //test/resources/ebclfsa_aarch64:qemu_config)", + "--qemu-image=$(location //test/resources/ebclfsa_aarch64:kernel)", + "--qemu-disk-image=$(location //test/resources/ebclfsa_aarch64:image)", + "--qemu-architecture=aarch64", + "--qemu-kernel-cmdline-file=$(location //test/resources/ebclfsa_aarch64:kernel_cmdline)", + ], + data = [ + "//test/resources/ebclfsa_aarch64:image", + "//test/resources/ebclfsa_aarch64:kernel", + "//test/resources/ebclfsa_aarch64:kernel_cmdline", + "//test/resources/ebclfsa_aarch64:qemu_config", + ], + plugins = [ + "//score/itf/plugins:qemu_plugin", + ], + tags = [ + "manual", + ], +) + test_suite( name = "qnx_qemu_tests", tags = [ "manual", ], tests = [ + ":test_ebclfsa_ping", ":test_qemu_bridge_network", ":test_qemu_bridge_network_no_dlt", ":test_qemu_port_forwarding", diff --git a/test/integration/test_ebclfsa_ping.py b/test/integration/test_ebclfsa_ping.py new file mode 100644 index 0000000..8c9a94a --- /dev/null +++ b/test/integration/test_ebclfsa_ping.py @@ -0,0 +1,29 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + + +def test_ping_from_host_to_target(target): + assert target.ping(timeout=10) + + +def test_ping_from_target_to_host(target): + cmd = """ + gw=$(ip route | awk '/default/ {print $3; exit}') + if [ -z \"$gw\" ]; then + echo \"No default gateway found\" >&2 + exit 1 + fi + ping -c 1 -W 5 \"$gw\" + """ + exit_code, _ = target.execute(cmd) + assert exit_code == 0 diff --git a/test/resources/BUILD b/test/resources/BUILD index 36bc323..f570082 100644 --- a/test/resources/BUILD +++ b/test/resources/BUILD @@ -81,6 +81,7 @@ pkg_tar( oci_image( name = "image", base = "@ubuntu_24_04", + target_compatible_with = ["@platforms//os:linux"], tars = [ ":dlt-pkg", ], @@ -90,6 +91,7 @@ oci_load( name = "image_load", image = ":image", repo_tags = ["score_itf_examples:latest"], + target_compatible_with = ["@platforms//os:linux"], visibility = [ "//test:__pkg__", # TODO: Should be only visible to examples diff --git a/test/resources/ebclfsa_aarch64/BUILD b/test/resources/ebclfsa_aarch64/BUILD new file mode 100644 index 0000000..27f42c1 --- /dev/null +++ b/test/resources/ebclfsa_aarch64/BUILD @@ -0,0 +1,87 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") + +genrule( + name = "extract-fastdev-image", + srcs = ["@eb_corbos_toolkit_fastdev_archive//file"], + outs = [ + "ebcl-qemuarm64/fastdev-ubuntu-ebclfsa-ebcl-qemuarm64.wic", + "ebcl-qemuarm64/fastdev-ubuntu-ebclfsa-ebcl-qemuarm64-vmlinux", + ], + cmd = "mkdir -p $(RULEDIR)/ebcl-qemuarm64 && tar xzf $(location @eb_corbos_toolkit_fastdev_archive//file) -C $(RULEDIR)/ebcl-qemuarm64 && chmod +w $(RULEDIR)/ebcl-qemuarm64/fastdev-ubuntu-ebclfsa-ebcl-qemuarm64.wic", + visibility = ["//visibility:private"], +) + +pkg_tar( + name = "network_config_tar", + srcs = [ + "config-overlay/etc/config/network/network", + ], + mode = "0644", + package_dir = "/etc/config/network", +) + +genrule( + name = "itf-image", + srcs = [ + ":extract-fastdev-image", + ":network_config_tar", + "build_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) \ + $(RULEDIR) \ + $(RULEDIR)/ebcl-qemuarm64 \ + $(RULEDIR)/ebcl-qemuarm64-itf \ + $(location :network_config_tar) + """, + visibility = ["//visibility:private"], +) + +filegroup( + name = "image", + srcs = [":ebcl-qemuarm64-itf/fastdev-ubuntu-ebclfsa-ebcl-qemuarm64.wic"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "kernel", + srcs = [":ebcl-qemuarm64-itf/fastdev-ubuntu-ebclfsa-ebcl-qemuarm64-vmlinux"], + visibility = ["//visibility:public"], +) + +exports_files( + [ + "kernel_cmdline.txt", + "qemu_config.json", + ], + visibility = ["//:__subpackages__"], +) + +filegroup( + name = "qemu_config", + srcs = ["qemu_config.json"], + visibility = ["//:__subpackages__"], +) + +filegroup( + name = "kernel_cmdline", + srcs = ["kernel_cmdline.txt"], + visibility = ["//visibility:public"], +) diff --git a/test/resources/ebclfsa_aarch64/build_image.sh b/test/resources/ebclfsa_aarch64/build_image.sh new file mode 100644 index 0000000..4cb3a88 --- /dev/null +++ b/test/resources/ebclfsa_aarch64/build_image.sh @@ -0,0 +1,104 @@ +#!/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 +# ******************************************************************************* + +set -euxo pipefail + +if [[ $# -lt 4 ]]; then + echo "Error: Expected at least 4 arguments (working directory, image source location, image target location, and one or more tar files to deploy relative to the root of the image)" >&2 + exit 1 +fi + +if ! command -v sshpass &> /dev/null; then + echo "Error: sshpass is not installed. Please install it to proceed." >&2 + exit 1 +fi + +WORKING_DIR="$1" +IMAGE_SOURCE="$2" +IMAGE_TARGET="$3" +shift 3 +DEPLOY_SRCS=("$@") + +if [[ ! -d "${IMAGE_SOURCE}" ]]; then + echo "Error: Image source location is not a directory: ${IMAGE_SOURCE}" >&2 + exit 1 +fi + +if ! find "${IMAGE_SOURCE}" -maxdepth 1 \( -name "*.wic" -type f -o -name "*.wic" -type l \) | grep -q .; then + echo "Error: No .wic file found in ${IMAGE_SOURCE}" >&2 + exit 1 +fi + +if ! find "${IMAGE_SOURCE}" -maxdepth 1 \( -name "*vmlinux" -type f -o -name "*vmlinux" -type l \) | grep -q .; then + echo "Error: No vmlinux file found in ${IMAGE_SOURCE}" >&2 + exit 1 +fi + +if [[ -e "${IMAGE_TARGET}" && ! -d "${IMAGE_TARGET}" ]]; then + echo "Error: Image target location exists but is not a directory: ${IMAGE_TARGET}" >&2 + exit 1 +fi + +for src in "${DEPLOY_SRCS[@]}"; do + if [[ ! -f "${src}" ]]; then + echo "Error: Deploy source file does not exist: ${src}" >&2 + exit 1 + fi + if [[ ! "${src}" =~ \.tar(\.(gz|bz2|xz))?$ ]]; then + echo "Error: Deploy source is not a tar file: ${src}" >&2 + exit 1 + fi +done + +mkdir -p "${IMAGE_TARGET}" +rm -f "${IMAGE_TARGET:?}/"* +cp -L "${IMAGE_SOURCE}"/* "${IMAGE_TARGET}"/ + +IMAGE=$(find "${IMAGE_TARGET}" -maxdepth 1 \( -name "*.wic" -type f -o -name "*.wic" -type l \) -print -quit) +KERNEL=$(find "${IMAGE_TARGET}" -maxdepth 1 \( -name "*vmlinux" -type f -o -name "*vmlinux" -type l \) -print -quit) + +# The image file must be writable for qemu overlay handling. +chmod +w "${IMAGE}" + +qemu-system-aarch64 -m 2048 -cpu cortex-a53 \ + -kernel "${KERNEL}" \ + -machine "virt,virtualization=true,gic-version=3" \ + -smp 8 \ + -device virtio-blk-device,drive=vd0 -drive if=none,format=raw,file="${IMAGE}",id=vd0 \ + -append "root=/dev/vda1 sdk_enable lisa_syscall_whitelist=2026 rw sharedmem.enable_sharedmem=0 init=/usr/bin/ebclfsa-cflinit" \ + -netdev user,id=net0,net=192.168.7.0/24,dhcpstart=192.168.7.2,dns=192.168.7.3,host=192.168.7.5,hostfwd=tcp::2222-:22 \ + -device virtio-net-device,netdev=net0 \ + -pidfile "${WORKING_DIR}/qemu.pid" \ + -nographic > "${WORKING_DIR}/qemu_deployment.log" & + +for _ in {1..60}; do + if sshpass -p linux ssh -o StrictHostKeyChecking=no -o ConnectTimeout=1 -p 2222 root@localhost true 2>/dev/null; then + break + fi + sleep 1 +done + +for src in "${DEPLOY_SRCS[@]}"; do + sshpass -p linux scp -rp -o StrictHostKeyChecking=no -P 2222 "${src}" root@localhost:/ + sshpass -p linux ssh -o StrictHostKeyChecking=no -p 2222 root@localhost "tar -xf /$(basename "${src}") -C /" + sshpass -p linux ssh -o StrictHostKeyChecking=no -p 2222 root@localhost "rm -f /$(basename "${src}")" +done + +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" +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 + wait "$(cat "${WORKING_DIR}/qemu.pid")" 2>/dev/null || true + rm -f "${WORKING_DIR}/qemu.pid" +fi diff --git a/test/resources/ebclfsa_aarch64/config-overlay/etc/config/network/network b/test/resources/ebclfsa_aarch64/config-overlay/etc/config/network/network new file mode 100644 index 0000000..f2a8117 --- /dev/null +++ b/test/resources/ebclfsa_aarch64/config-overlay/etc/config/network/network @@ -0,0 +1,12 @@ +config interface 'loopback' + option device 'lo' + option proto 'static' + option ipaddr '127.0.0.1' + option netmask '255.0.0.0' + +config interface 'lan' + option device 'eth0' + option proto 'static' + option ipaddr '169.254.158.190' + option netmask '255.255.0.0' + option gateway '169.254.21.88' diff --git a/test/resources/ebclfsa_aarch64/kernel_cmdline.txt b/test/resources/ebclfsa_aarch64/kernel_cmdline.txt new file mode 100644 index 0000000..83295ab --- /dev/null +++ b/test/resources/ebclfsa_aarch64/kernel_cmdline.txt @@ -0,0 +1 @@ +root=/dev/vda1 sdk_enable lisa_syscall_whitelist=2026 rw sharedmem.enable_sharedmem=0 init=/usr/bin/ebclfsa-cflinit diff --git a/test/resources/ebclfsa_aarch64/qemu_config.json b/test/resources/ebclfsa_aarch64/qemu_config.json new file mode 100644 index 0000000..3bef2b9 --- /dev/null +++ b/test/resources/ebclfsa_aarch64/qemu_config.json @@ -0,0 +1,12 @@ +{ + "networks": [ + { + "name": "tap0", + "ip_address": "169.254.158.190", + "gateway": "169.254.21.88" + } + ], + "ssh_port": 22, + "qemu_num_cores": 2, + "qemu_ram_size": "1G" +} diff --git a/test/unit/BUILD b/test/unit/BUILD index 68c32da..66cbc15 100644 --- a/test/unit/BUILD +++ b/test/unit/BUILD @@ -15,11 +15,13 @@ load("//:defs.bzl", "py_itf_unittest") py_itf_unittest( name = "test_ping", srcs = ["test_ping.py"], + target_compatible_with = ["@platforms//os:linux"], ) py_itf_unittest( name = "test_qemu_config_schema", srcs = ["test_qemu_config_schema.py"], + target_compatible_with = ["@platforms//os:linux"], deps = ["//score/itf/plugins/qemu:config"], )