diff --git a/qnx_qemu/BUILD b/qnx_qemu/BUILD index 0ee32b983b..17961a954e 100644 --- a/qnx_qemu/BUILD +++ b/qnx_qemu/BUILD @@ -133,3 +133,22 @@ py_itf_test( "target_config.json", ], ) + +py_itf_test( + name = "test_lifecycle_qemu", + srcs = [ + "test/itf/test_lifecycle.py", + ], + args = [ + "--target_config=$(location target_config.json)", + "--ecu=s_core_ecu_qemu", + "--qemu_image=$(location //build:init)", + ], + plugins = [ + "itf.plugins.base.base_plugin", + ], + data = [ + "//build:init", + "target_config.json", + ], +) diff --git a/qnx_qemu/MODULE.bazel b/qnx_qemu/MODULE.bazel index 5fa697079b..97400a963c 100644 --- a/qnx_qemu/MODULE.bazel +++ b/qnx_qemu/MODULE.bazel @@ -89,6 +89,25 @@ register_toolchains("@toolchains_qnx_ifs//:ifs_x86_64") ############################################################################### bazel_dep(name = "rules_cc", version = "0.1.1") bazel_dep(name = "score_itf", version = "0.1.0") + +bazel_dep(name = "flatbuffers", version = "25.9.23") +git_override( + module_name = "flatbuffers", + commit = "187240970746d00bbd26b0f5873ed54d2477f9f3", + remote = "https://github.com/google/flatbuffers.git", +) + +bazel_dep(name = "score_lifecycle_health") +git_override( + module_name = "score_lifecycle_health", + commit = "7ede6519c60f3c82c1227a8b53fdf4acfefc84a3", + remote = "https://github.com/etas-contrib/score_lifecycle.git", +) +# local_path_override( +# module_name = "score_lifecycle_health", +# path = "score_lifecycle_health", +# ) + bazel_dep(name = "score_baselibs", version = "0.1.3") bazel_dep(name = "score_communication", version = "0.1.1") @@ -110,4 +129,5 @@ git_override( module_name = "trlc", commit = "650b51a47264a4f232b3341f473527710fc32669", # trlc-2.0.2 release remote = "https://github.com/bmw-software-engineering/trlc.git", -) \ No newline at end of file +) + diff --git a/qnx_qemu/build/BUILD b/qnx_qemu/build/BUILD index 077d30e0e9..74d252e283 100644 --- a/qnx_qemu/build/BUILD +++ b/qnx_qemu/build/BUILD @@ -42,6 +42,14 @@ filegroup( visibility = ["//visibility:private"], ) +filegroup( + name = "lifecycle_launch_manager", + srcs = [ + "@score_lifecycle_health//src/launch_manager_daemon:launch_manager", + ], + visibility = ["//visibility:public"], +) + qnx_ifs( name = "init", build_file = "init.build", @@ -51,11 +59,27 @@ qnx_ifs( ":configs", "@score_scrample//src:scrample", "//scrample_integration:etc_configs", - "@score_persistency//tests/cpp_test_scenarios:cpp_test_scenarios", + "@score_persistency//tests/cpp_test_scenarios:cpp_test_scenarios", + "@score_lifecycle_health//src/launch_manager_daemon:launch_manager", + "@score_lifecycle_health//examples/cpp_supervised_app", + "@score_lifecycle_health//examples/control_application:control_daemon", + "//lifecycle_integration:generate_launch_manager_config", + "//lifecycle_integration:generate_launch_manager_health_config", + "//lifecycle_integration:generate_supervised_app_health_config", + "@score_lifecycle_health//examples/control_application:lmcontrol", + "//lifecycle_integration:lifecycle_start" ], ext_repo_maping = { "SCRAMPLE_PATH": "$(location @score_scrample//src:scrample)", "CPP_TEST_SCENARIOS_PATH": "$(location @score_persistency//tests/cpp_test_scenarios:cpp_test_scenarios)", + "LIFECYCLE_LAUNCH_MANAGER_EXE": "$(location @score_lifecycle_health//src/launch_manager_daemon:launch_manager)", + "LIFECYCLE_LAUNCH_MANAGER_LM_CONFIG": "$(location //lifecycle_integration:generate_launch_manager_config)", + "LIFECYCLE_LAUNCH_MANAGER_HM_CONFIG": "$(location //lifecycle_integration:generate_launch_manager_health_config)", + "LIFECYCLE_CPP_SUPERVISED_APP_EXE": "$(location @score_lifecycle_health//examples/cpp_supervised_app)", + "LIFECYCLE_CPP_SUPERVISED_APP_CONFIG": "$(location //lifecycle_integration:generate_supervised_app_health_config)", + "LIFECYCLE_STATE_MANAGER_EXE": "$(location @score_lifecycle_health//examples/control_application:control_daemon)", + "LIFECYCLE_STATE_MANAGER_CONTROL" : "$(location @score_lifecycle_health//examples/control_application:lmcontrol)", + "LIFECYCLE_DEMO_START": "$(location //lifecycle_integration:lifecycle_start)" }, visibility = [ "//:__pkg__" diff --git a/qnx_qemu/build/system.build b/qnx_qemu/build/system.build index baab76a47f..97e4160ecf 100644 --- a/qnx_qemu/build/system.build +++ b/qnx_qemu/build/system.build @@ -198,6 +198,7 @@ hostname route dhcpcd # DHCP client daemon for automatic network configuration tcpdump # Network packet capture tool for Wireshark analysis +slay # Send signals to processes by process name /usr/lib/ssh/sftp-server=${QNX_TARGET}/${PROCESSOR}/usr/libexec/sftp-server # File transfer server to enable scp ############################################# @@ -281,3 +282,11 @@ pci/pci_debug2.so # Enhanced PCI debugging support [perms=777] /scrample = ${SCRAMPLE_PATH} [perms=777] /cpp_tests_persistency = ${CPP_TEST_SCENARIOS_PATH} +[perms=777] /lifecycle/launch_manager/launch_manager = ${LIFECYCLE_LAUNCH_MANAGER_EXE} +[perms=777] /lifecycle/launch_manager/etc/lm_demo.bin = ${LIFECYCLE_LAUNCH_MANAGER_LM_CONFIG} +[perms=777] /lifecycle/launch_manager/etc/hm_demo.bin = ${LIFECYCLE_LAUNCH_MANAGER_HM_CONFIG} +[perms=777] /lifecycle/cpp_supervised_app/cpp_supervised_app = ${LIFECYCLE_CPP_SUPERVISED_APP_EXE} +[perms=777] /lifecycle/cpp_supervised_app/etc/supervised_app_demo.bin = ${LIFECYCLE_CPP_SUPERVISED_APP_CONFIG} +[perms=777] /lifecycle/control_daemon/control_daemon = ${LIFECYCLE_STATE_MANAGER_EXE} +[perms=777] /lifecycle/control_daemon/lmcontrol = ${LIFECYCLE_STATE_MANAGER_CONTROL} +[perms=777] /lifecycle/start.sh = ${LIFECYCLE_DEMO_START} \ No newline at end of file diff --git a/qnx_qemu/lifecycle_integration/BUILD b/qnx_qemu/lifecycle_integration/BUILD new file mode 100644 index 0000000000..c42ca98087 --- /dev/null +++ b/qnx_qemu/lifecycle_integration/BUILD @@ -0,0 +1,52 @@ +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +genrule( + name = "generate_launch_manager_config", + srcs = ["@score_lifecycle_health//src/launch_manager_daemon:lm_flatcfg_fbs", ":launch_manager_config"], + outs = ["lm_demo.bin"], + tools = ["@flatbuffers//:flatc"], + cmd = "$(location @flatbuffers//:flatc) --binary $(SRCS) && mv lm_demo.bin $(location lm_demo.bin)", + visibility = ["//visibility:public"], +) + +genrule( + name = "generate_launch_manager_health_config", + srcs = ["@score_lifecycle_health//src/launch_manager_daemon/health_monitor_lib:hm_flatcfg_fbs", ":launch_manager_health_config"], + outs = ["hm_demo.bin"], + tools = ["@flatbuffers//:flatc"], + cmd = "$(location @flatbuffers//:flatc) --binary $(SRCS) && mv hm_demo.bin $(location hm_demo.bin)", + visibility = ["//visibility:public"], +) + +genrule( + name = "generate_supervised_app_health_config", + srcs = ["@score_lifecycle_health//src/launch_manager_daemon/health_monitor_lib:hm_flatcfg_fbs", ":supervised_app_health_config"], + outs = ["supervised_app_demo.bin"], + tools = ["@flatbuffers//:flatc"], + cmd = "$(location @flatbuffers//:flatc) --binary $(SRCS) && mv supervised_app_demo.bin $(location supervised_app_demo.bin)", + visibility = ["//visibility:public"], +) + +filegroup( + name = "lifecycle_start", + srcs = ["start.sh"], + visibility = ["//visibility:public"] +) + +filegroup( + name = "launch_manager_config", + srcs = ["lm_demo.json"], + visibility = ["//visibility:public"] +) + +filegroup( + name = "launch_manager_health_config", + srcs = ["hm_demo.json"], + visibility = ["//visibility:public"] +) + +filegroup( + name = "supervised_app_health_config", + srcs = ["supervised_app_demo.json"], + visibility = ["//visibility:public"] +) diff --git a/qnx_qemu/lifecycle_integration/hm_demo.json b/qnx_qemu/lifecycle_integration/hm_demo.json new file mode 100644 index 0000000000..b0ea20fa2f --- /dev/null +++ b/qnx_qemu/lifecycle_integration/hm_demo.json @@ -0,0 +1,103 @@ +{ + "versionMajor": 8, + "versionMinor": 0, + "process": [ + { + "index": 0, + "shortName": "demo_application0", + "identifier": "demo_app0_MainPG", + "processType": "REGULAR_PROCESS", + "refProcessGroupStates": [ + { + "identifier": "MainPG/Startup" + } + ], + "processExecutionErrors": [ + { + "processExecutionError": 1 + } + ] + } + ], + "hmMonitorInterface": [ + { + "instanceSpecifier": "demo/demo_application0/Port1", + "processShortName": "demo_application0", + "portPrototype": "Port1", + "interfacePath": "demo_application_0_MainPG", + "refProcessIndex": 0, + "permittedUid": 0 + } + ], + "hmSupervisionCheckpoint": [ + { + "shortName": "Checkpoint0_1", + "checkpointId": 1, + "refInterfaceIndex": 0 + } + ], + "hmAliveSupervision": [ + { + "ruleContextKey": "AliveSupervision0", + "refCheckPointIndex": 0, + "aliveReferenceCycle": 100.0, + "minAliveIndications": 1, + "maxAliveIndications": 3, + "isMinCheckDisabled": false, + "isMaxCheckDisabled": false, + "failedSupervisionCyclesTolerance": 1, + "refProcessIndex": 0, + "refProcessGroupStates": [ + { + "identifier": "MainPG/Startup" + } + ] + } + ], + "hmDeadlineSupervision": [], + "hmLogicalSupervision": [], + "hmLocalSupervision": [ + { + "ruleContextKey": "LocalSupervision0", + "infoRefInterfacePath": "demo_application_0", + "hmRefAliveSupervision": [ + { + "refAliveSupervisionIdx": 0 + } + ], + "hmRefDeadlineSupervision": [], + "hmRefLogicalSupervision": [] + } + ], + "hmGlobalSupervision": [ + { + "ruleContextKey": "GlobalSupervision_MainPG", + "isSeverityCritical": false, + "localSupervision": [ + { + "refLocalSupervisionIndex": 0 + } + ], + "refProcesses": [ + { + "index": 0 + } + ], + "refProcessGroupStates": [ + { + "identifier": "MainPG/Startup" + } + ] + } + ], + "hmRecoveryNotification": [ + { + "shortName": "RecoveryNotification_MainPG", + "recoveryNotificationTimeout": 4000.0, + "processGroupMetaModelIdentifier": "MainPG/Recovery", + "refGlobalSupervisionIndex": 0, + "instanceSpecifier": "", + "shouldFireWatchdog": false + } + ] +} diff --git a/qnx_qemu/lifecycle_integration/lm_demo.json b/qnx_qemu/lifecycle_integration/lm_demo.json new file mode 100644 index 0000000000..321f54a254 --- /dev/null +++ b/qnx_qemu/lifecycle_integration/lm_demo.json @@ -0,0 +1,118 @@ +{ + "versionMajor": 7, + "versionMinor": 0, + "Process": [ + { + "identifier": "control_daemon", + "uid": 0, + "gid": 0, + "path": "/lifecycle/control_daemon/control_daemon", + "functionClusterAffiliation": "STATE_MANAGEMENT", + "numberOfRestartAttempts": 0, + "executable_reportingBehavior": "ReportsExecutionState", + "sgids": [], + "startupConfig": [ + { + "executionError": "1", + "schedulingPolicy": "SCHED_OTHER", + "schedulingPriority": "1", + "identifier": "control_daemon_startup_config", + "enterTimeoutValue": 1000, + "exitTimeoutValue": 1000, + "terminationBehavior": "ProcessIsNotSelfTerminating", + "executionDependency": [], + "processGroupStateDependency": [ + { + "stateMachine_name": "MainPG", + "stateName": "MainPG/Startup" + }, + { + "stateMachine_name": "MainPG", + "stateName": "MainPG/Recovery" + } + ], + "environmentVariable": [ + { + "key": "LD_LIBRARY_PATH", + "value": "/opt/lib" + }, + { + "key": "PROCESSIDENTIFIER", + "value": "control_daemon" + } + ], + "processArgument": [] + } + ] + }, + { + "identifier": "demo_app0_MainPG", + "uid": 0, + "gid": 0, + "path": "/lifecycle/cpp_supervised_app/cpp_supervised_app", + "numberOfRestartAttempts": 0, + "executable_reportingBehavior": "ReportsExecutionState", + "sgids": [], + "startupConfig": [ + { + "executionError": "1", + "schedulingPolicy": "SCHED_OTHER", + "schedulingPriority": "1", + "identifier": "demo_app_startup_config_0", + "enterTimeoutValue": 2000, + "exitTimeoutValue": 2000, + "terminationBehavior": "ProcessIsNotSelfTerminating", + "executionDependency": [ + { + "stateName": "Running", + "targetProcess_identifier": "/healthmonitorApp/healthmonitor" + } + ], + "processGroupStateDependency": [ + { + "stateMachine_name": "MainPG", + "stateName": "MainPG/Startup" + } + ], + "environmentVariable": [ + { + "key": "LD_LIBRARY_PATH", + "value": "/usr/lib/" + }, + { + "key": "PROCESSIDENTIFIER", + "value": "MainPG_app0" + }, + { + "key": "CONFIG_PATH", + "value": "/lifecycle/cpp_supervised_app/etc/supervised_app_demo.bin" + } + ], + "processArgument": [ + { + "argument": "-sdemo/demo_application0/Port1" + } + ] + } + ] + } + ], + "ModeGroup": [ + { + "identifier": "MainPG", + "initialMode_name": "Off", + "recoveryMode_name": "MainPG/Recovery", + "modeDeclaration": [ + { + "identifier": "MainPG/Off" + }, + { + "identifier": "MainPG/Startup" + }, + { + "identifier": "MainPG/Recovery" + } + ] + } + ] +} diff --git a/qnx_qemu/lifecycle_integration/start.sh b/qnx_qemu/lifecycle_integration/start.sh new file mode 100755 index 0000000000..eb137330fd --- /dev/null +++ b/qnx_qemu/lifecycle_integration/start.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# This script is required to run a simple smoke test via itf. +# It starts the launch manager, lets it run a couple of seconds and terminates it. + +cd /lifecycle/launch_manager +./launch_manager& +sleep 10 +slay launch_manager + +# Slay returns 1 if a single launch_manager process was found +retVal=$? +if [ $retVal -eq 1 ]; then + exit 0 +fi \ No newline at end of file diff --git a/qnx_qemu/lifecycle_integration/supervised_app_demo.json b/qnx_qemu/lifecycle_integration/supervised_app_demo.json new file mode 100644 index 0000000000..ee3f79c609 --- /dev/null +++ b/qnx_qemu/lifecycle_integration/supervised_app_demo.json @@ -0,0 +1,15 @@ + +{ + "versionMajor": 8, + "versionMinor": 0, + "process": [], + "hmMonitorInterface": [ + { + "instanceSpecifier": "demo/demo_application0/Port1", + "processShortName": "demo_application0", + "portPrototype": "Port1", + "interfacePath": "demo_application_0_MainPG", + "refProcessIndex":0 + } + ] +} diff --git a/qnx_qemu/test/itf/test_lifecycle.py b/qnx_qemu/test/itf/test_lifecycle.py new file mode 100644 index 0000000000..81ec367633 --- /dev/null +++ b/qnx_qemu/test/itf/test_lifecycle.py @@ -0,0 +1,41 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* +from itf.plugins.com.ping import ping +from itf.plugins.com.ssh import execute_command_output, execute_command, _read_output_with_timeout, command_with_etc +import logging + +logger = logging.getLogger(__name__) + + +def test_lifecycle_apps_are_deployed(target_fixture): + with target_fixture.sut.ssh() as ssh: + exit_code, stdout, stderr = execute_command_output( + ssh, "test -f /lifecycle/launch_manager/launch_manager &&" \ + "test -f /lifecycle/cpp_supervised_app/cpp_supervised_app &&" \ + "test -f /lifecycle/control_daemon/control_daemon" + ) + assert exit_code == 0, "SSH command failed" + +def test_lifecycle_test_app_is_running(target_fixture): + with target_fixture.sut.ssh() as ssh: + # Using a more complex cmd is not working with `execute_command_output` + # Therefore, the command had to be put into a separate script. + cmd = "/lifecycle/start.sh" + exit_code, stdout, stderr = execute_command_output(ssh, cmd, + timeout = 30, max_exec_time = 180, + logger_in = logger, verbose = True) + + logger.info (stdout) + logger.info (stderr) + + assert exit_code == 0, "SSH command failed"