diff --git a/.bazelrc b/.bazelrc index d990313d0d9..b4eeba5169e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -13,7 +13,6 @@ build --tool_java_language_version=17 build --java_runtime_version=remotejdk_17 build --tool_java_runtime_version=remotejdk_17 -build:_common --@score_baselibs//score/mw/log/flags:KRemote_Logging=False build:_common --@score_baselibs//score/json:base_library=nlohmann build:_common --@score_baselibs//score/memory/shared/flags:use_typedshmd=False build:_common --@score_communication//score/mw/com/flags:tracing_library=stub @@ -22,6 +21,14 @@ build:_common --host_platform=@score_bazel_platforms//:x86_64-linux-gcc_12.2.0-p build:_common --extra_toolchains=@score_toolchains_rust//toolchains/ferrocene:ferrocene_x86_64_unknown_linux_gnu build:_common --extra_toolchains=@score_gcc_x86_64_toolchain//:x86_64-linux-gcc_12.2.0-posix +# Flags needed by datarouter +build:_common --@score_logging//score/datarouter/build_configuration_flags:persistent_logging=False +build:_common --@score_logging//score/datarouter/build_configuration_flags:persistent_config_feature_enabled=False +build:_common --@score_logging//score/datarouter/build_configuration_flags:enable_nonverbose_dlt=False +build:_common --@score_logging//score/datarouter/build_configuration_flags:enable_dynamic_configuration=False +build:_common --@score_logging//score/datarouter/build_configuration_flags:file_transfer=False +build:_common --@score_logging//score/datarouter/build_configuration_flags:use_local_vlan=True + build:qnx-x86_64 --config=_common build:qnx-x86_64 --noexperimental_merged_skyframe_analysis_execution build:qnx-x86_64 --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 diff --git a/feature_integration_tests/configs/BUILD b/feature_integration_tests/configs/BUILD index 8556521da4d..dce9a78284e 100644 --- a/feature_integration_tests/configs/BUILD +++ b/feature_integration_tests/configs/BUILD @@ -12,6 +12,8 @@ # ******************************************************************************* exports_files( [ + "dlt_config_qnx_x86_64.json", + "dlt_config_x86_64.json", "qemu_bridge_config.json", ], ) diff --git a/feature_integration_tests/configs/datarouter/BUILD b/feature_integration_tests/configs/datarouter/BUILD new file mode 100644 index 00000000000..ab722788def --- /dev/null +++ b/feature_integration_tests/configs/datarouter/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +exports_files([ + "etc/logging.json", + "etc/log-channels.json", +]) + +filegroup( + name = "etc_configs", + srcs = [ + "etc/log-channels.json", + "etc/logging.json", + ], + visibility = ["//visibility:public"], +) diff --git a/feature_integration_tests/configs/datarouter/etc/log-channels.json b/feature_integration_tests/configs/datarouter/etc/log-channels.json new file mode 100644 index 00000000000..58949e662e0 --- /dev/null +++ b/feature_integration_tests/configs/datarouter/etc/log-channels.json @@ -0,0 +1,62 @@ +{ + "channels": { + "3491": { + "address": "0.0.0.0", + "channelThreshold": "kError", + "dstAddress": "239.255.42.99", + "dstPort": 3490, + "ecu": "SCPP", + "port": 3491 + }, + "3492": { + "address": "0.0.0.0", + "channelThreshold": "kInfo", + "dstAddress": "239.255.42.99", + "dstPort": 3490, + "ecu": "SCPP", + "port": 3492 + }, + "3493": { + "address": "0.0.0.0", + "channelThreshold": "kVerbose", + "dstAddress": "239.255.42.99", + "dstPort": 3490, + "ecu": "SCPP", + "port": 3493 + } + }, + "channelAssignments": { + "DR": { + "": [ + "3492" + ], + "CTX1": [ + "3492", + "3493" + ], + "STAT": [ + "3492" + ] + }, + "-NI-": { + "": [ + "3491" + ] + } + }, + "defaultChannel": "3493", + "defaultThresold": "kVerbose", + "messageThresholds": { + "": { + "": "kInfo" + }, + "DR": { + "": "kVerbose", + "CTX1": "kVerbose", + "STAT": "kDebug" + }, + "-NI-": { + "": "kVerbose" + } + } +} diff --git a/feature_integration_tests/configs/datarouter/etc/logging.json b/feature_integration_tests/configs/datarouter/etc/logging.json new file mode 100644 index 00000000000..4ea4800a5a8 --- /dev/null +++ b/feature_integration_tests/configs/datarouter/etc/logging.json @@ -0,0 +1,10 @@ +{ + "appId": "DR", + "appDesc": "Data Router", + "logLevel": "kInfo", + "logMode": "kRemote", + "ringBufferSize": 786432, + "numberOfSlots": 4, + "slotSizeBytes": 4096, + "datarouterUid": 1051 +} diff --git a/feature_integration_tests/configs/dlt_config_qnx_x86_64.json b/feature_integration_tests/configs/dlt_config_qnx_x86_64.json new file mode 100644 index 00000000000..45ea1f61a0f --- /dev/null +++ b/feature_integration_tests/configs/dlt_config_qnx_x86_64.json @@ -0,0 +1,7 @@ +{ + "host_ip": "169.254.21.88", + "target_ip": "169.254.21.88", + "multicast_ips": [ + "239.255.42.99" + ] +} diff --git a/feature_integration_tests/configs/dlt_config_x86_64.json b/feature_integration_tests/configs/dlt_config_x86_64.json new file mode 100644 index 00000000000..72fb4d9a481 --- /dev/null +++ b/feature_integration_tests/configs/dlt_config_x86_64.json @@ -0,0 +1,7 @@ +{ + "host_ip": "172.17.0.1", + "target_ip": "172.17.0.1", + "multicast_ips": [ + "239.255.42.99" + ] +} diff --git a/feature_integration_tests/itf/BUILD b/feature_integration_tests/itf/BUILD index cf3c3cf78bf..2cb6c0c8d47 100644 --- a/feature_integration_tests/itf/BUILD +++ b/feature_integration_tests/itf/BUILD @@ -11,11 +11,12 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* load("@score_itf//:defs.bzl", "py_itf_test") -load("@score_itf//score/itf/plugins:plugins.bzl", "docker", "qemu") +load("@score_itf//score/itf/plugins:plugins.bzl", "dlt", "docker", "qemu") filegroup( name = "all_tests", srcs = [ + "test_remote_logging.py", "test_showcases.py", "test_ssh.py", ], @@ -27,13 +28,16 @@ py_itf_test( ":all_tests", ], args = [ + "--dlt-config=$(location //feature_integration_tests/configs:dlt_config_x86_64.json)", "--docker-image-bootstrap=$(location //images/linux_x86_64:image_tarball)", "--docker-image=score_showcases:latest", ], data = [ + "//feature_integration_tests/configs:dlt_config_x86_64.json", "//images/linux_x86_64:image_tarball", ], plugins = [ + dlt, docker, ], tags = [ @@ -47,14 +51,17 @@ py_itf_test( ":all_tests", ], args = [ + "--dlt-config=$(location //feature_integration_tests/configs:dlt_config_qnx_x86_64.json)", "--qemu-config=$(location //feature_integration_tests/configs:qemu_bridge_config.json)", "--qemu-image=$(location //images/qnx_x86_64:image)", ], data = [ + "//feature_integration_tests/configs:dlt_config_qnx_x86_64.json", "//feature_integration_tests/configs:qemu_bridge_config.json", "//images/qnx_x86_64:image", ], plugins = [ + dlt, qemu, ], tags = [ diff --git a/feature_integration_tests/itf/test_remote_logging.py b/feature_integration_tests/itf/test_remote_logging.py new file mode 100644 index 00000000000..58101d41ab4 --- /dev/null +++ b/feature_integration_tests/itf/test_remote_logging.py @@ -0,0 +1,113 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +import logging +import os +import time + +import pytest + +from score.itf.plugins.dlt.dlt_receive import Protocol +from score.itf.plugins.dlt.dlt_window import DltWindow + +logger = logging.getLogger(__name__) + + +# Datarouter messages with Context ID "STAT" sent every ~5s hence 10s to reliably capture and verify +CAPTURE_DURATION_SECONDS = 10 + +# DLT message identifiers for datarouter statistics +APP_ID = "DR" +CTX_ID = "STAT" + +_QNX_DATAROUTER_CHECK_CMD = "/proc/boot/pidin | /proc/boot/grep datarouter" +_LINUX_DATAROUTER_CHECK_CMD = "ps -ef | grep datarouter | grep -v grep" + +# pathspace ability provides the datarouter access to the `procnto` pathname prefix space +# required for mw/com message passing with mw::log frontend +_QNX_DATAROUTER_START_CMD = ( + "cd /usr/bin/datarouter && nohup on -A nonroot,allow,pathspace -u 1051:1091 " + "./datarouter --no_adaptive_runtime > /dev/null 2>&1 &" +) +_LINUX_DATAROUTER_START_CMD = "cd /usr/bin/datarouter && nohup ./datarouter --no_adaptive_runtime > /dev/null 2>&1 &" +# Multicast route directs all multicast traffic (224.0.0.0/4) out of the containers +# network interface required for DLT messages to reach the host via the Docker bridge. +_LINUX_CHECK_MULTICAST_ROUTE_CMD = "ip route add 224.0.0.0/4 dev eth0 2>/dev/null || true" +_DATAROUTER_STARTUP_TIMEOUT_SEC = 2 + + +def _is_qnx(target): + _, out = target.execute("uname -s") + return b"QNX" in out + + +@pytest.fixture +def datarouter_running(target): + is_qnx = _is_qnx(target) + + if is_qnx: + check_cmd = _QNX_DATAROUTER_CHECK_CMD + start_cmd = _QNX_DATAROUTER_START_CMD + else: + check_cmd = _LINUX_DATAROUTER_CHECK_CMD + start_cmd = _LINUX_DATAROUTER_START_CMD + target.execute(_LINUX_CHECK_MULTICAST_ROUTE_CMD) + + exit_code, out = target.execute(check_cmd) + output = out.decode(errors="replace") + + if "datarouter" not in output: + exit_code, _ = target.execute("test -x /usr/bin/datarouter") + if exit_code != 0: + pytest.fail("Datarouter binary not found at /usr/bin/datarouter on target") + + logger.info("Datarouter not running. Starting Datarouter..") + exit_code, out = target.execute(start_cmd) + logger.debug("Start command exit_code=%s", exit_code) + time.sleep(_DATAROUTER_STARTUP_TIMEOUT_SEC) + + _, out = target.execute(check_cmd) + if "datarouter" not in out.decode(errors="replace"): + pytest.fail("Failed to start datarouter on target") + logger.info("Datarouter started successfully..") + else: + logger.info("Datarouter already running!") + yield + + +def test_remote_logging(datarouter_running, dlt_config): + """Verifies remote logging of Dataraouter + + Starts Datarouter on the target if not already running, then + the dlt_receive captures DLT messages on the test host + on the multicast group for a fixed duration and checks for + expected 'STAT' log messages in the captured DTL logs. + """ + with DltWindow( + protocol=Protocol.UDP, + host_ip=dlt_config.host_ip, + multicast_ips=dlt_config.multicast_ips, + print_to_stdout=True, + binary_path=dlt_config.dlt_receive_path, + ) as window: + time.sleep(CAPTURE_DURATION_SECONDS) + + record = window.record(filters=[(APP_ID, CTX_ID)]) + messages = record.find(query=dict(apid=APP_ID, ctid=CTX_ID)) + message_count = len(messages) + + logger.debug("Found %d messages with app_id: %s, context_id: %s", message_count, APP_ID, CTX_ID) + + assert message_count > 1, ( + f"Expected atleast one DLT message with app_id: {APP_ID} and context_id: {CTX_ID}, but got {message_count}" + ) diff --git a/images/linux_x86_64/BUILD b/images/linux_x86_64/BUILD index 61ac4d1bce1..71cdbd384cd 100644 --- a/images/linux_x86_64/BUILD +++ b/images/linux_x86_64/BUILD @@ -28,11 +28,30 @@ sh_binary( }, ) +pkg_tar( + name = "datarouter_bin_tar", + srcs = ["@score_logging//score/datarouter"], + mode = "0755", + package_dir = "/usr/bin/datarouter", +) + +pkg_tar( + name = "datarouter_etc_tar", + srcs = [ + "//feature_integration_tests/configs/datarouter:etc/log-channels.json", + "//feature_integration_tests/configs/datarouter:etc/logging.json", + ], + mode = "0644", + package_dir = "/usr/bin/datarouter/etc", +) + oci_image( name = "image", base = "@ubuntu_22_04", tars = [ "//showcases:showcases_pkg_tar", + ":datarouter_bin_tar", + ":datarouter_etc_tar", ], ) diff --git a/images/qnx_x86_64/build/BUILD b/images/qnx_x86_64/build/BUILD index 897ebfd791c..a9aabe11678 100644 --- a/images/qnx_x86_64/build/BUILD +++ b/images/qnx_x86_64/build/BUILD @@ -50,12 +50,15 @@ qnx_ifs( ":system.build", ":system_dir", "//feature_integration_tests/configs:etc_configs", + "//feature_integration_tests/configs/datarouter:etc_configs", "//showcases", + "@score_logging//score/datarouter", "@score_persistency//tests/test_scenarios/cpp:test_scenarios", ], build_file = "init.build", ext_repo_maping = { "BUNDLE_PATH": "$(location //showcases:showcases)", + "DATAROUTER_PATH": "$(location @score_logging//score/datarouter:datarouter)", }, visibility = [ "//visibility:public", diff --git a/images/qnx_x86_64/build/system.build b/images/qnx_x86_64/build/system.build index bf1226f22d7..16f55a4607f 100644 --- a/images/qnx_x86_64/build/system.build +++ b/images/qnx_x86_64/build/system.build @@ -66,7 +66,7 @@ libpam.so.2 # Pluggable Authentication Modules libr # Note: /bin/sh symlink already defined in init.build as /proc/boot/ksh toybox # Minimal Unix utilities collection (replaces many GNU tools) [type=link] cp=toybox # Copy files and directories -[type=link] ls=toybox # List directory contents +[type=link] ls=toybox # List directory contents [type=link] cat=toybox # Display file contents [type=link] chmod=toybox # Change file permissions [type=link] rm=toybox # Remove files and directories @@ -197,7 +197,7 @@ hostname route dhcpcd # DHCP client daemon for automatic network configuration tcpdump # Network packet capture tool for Wireshark analysis -slay +slay /usr/lib/ssh/sftp-server=${QNX_TARGET}/${PROCESSOR}/usr/libexec/sftp-server # File transfer server to enable scp ############################################# @@ -284,6 +284,10 @@ pci/pci_debug2.so # Enhanced PCI debugging support [perms=777] /scrample = ${SCRAMPLE_PATH} [perms=777] /cpp_tests_persistency = ${CPP_TEST_SCENARIOS_PATH} +[perms=777] /usr/bin/datarouter/datarouter = ${DATAROUTER_PATH} +[perms=644] /usr/bin/datarouter/etc/logging.json = ${MAIN_BUILD_FILE_DIR}/../../../feature_integration_tests/configs/datarouter/etc/logging.json +[perms=644] /usr/bin/datarouter/etc/log-channels.json = ${MAIN_BUILD_FILE_DIR}/../../../feature_integration_tests/configs/datarouter/etc/log-channels.json + # Common showcases bundle [perms=777] / = ${BUNDLE_PATH} diff --git a/images/qnx_x86_64/configs/network_setup_dhcp.sh b/images/qnx_x86_64/configs/network_setup_dhcp.sh index 785fced4199..da2b1db8b15 100644 --- a/images/qnx_x86_64/configs/network_setup_dhcp.sh +++ b/images/qnx_x86_64/configs/network_setup_dhcp.sh @@ -89,4 +89,12 @@ fi # Configure system network settings sysctl -w net.inet.icmp.bmcastecho=1 > /dev/null # Enable ICMP broadcast echo (responds to broadcast pings) +# The datarouter sends DLT messages via UDP multicast to +# '239.255.42.99:3490' (configured in log-channels.json). +# Adding multicast route via vtnet0 interface directs all +# multicast traffic ('224.0.0.0/4') out the guest network +# interface so it reaches the host via the QEMU TAP device. +echo "Adding multicast route" +route add -net 224.0.0.0 -netmask 240.0.0.0 -interface vtnet0 + echo "---> Network configuration completed"