From 4da5a82ba4b3014e6383bf3367022059a2e44bfb Mon Sep 17 00:00:00 2001 From: Paul Quiring Date: Mon, 13 Jul 2026 15:19:08 +0200 Subject: [PATCH 1/2] Launch Manager loads the new configuration file --- config/BUILD | 14 + examples/demo_verification/test_examples.py | 9 +- score/launch_manager/src/daemon/BUILD | 11 +- .../details/daemon/AliveMonitorImpl.cpp | 32 +- .../details/daemon/AliveMonitorImpl.hpp | 39 +- .../src/alive_monitor/details/daemon/BUILD | 26 +- .../details/daemon/PhmDaemon.cpp | 21 +- .../details/daemon/PhmDaemon.hpp | 67 ++- .../details/daemon/SwClusterHandler.cpp | 7 + .../details/daemon/SwClusterHandler.hpp | 6 + .../src/alive_monitor/details/factory/BUILD | 29 +- .../details/factory/FlatCfgFactory.cpp | 13 +- .../details/factory/FlatCfgFactory.hpp | 23 +- .../details/factory/FlatCfgFactory_new.cpp | 334 +++++++++++ .../details/factory/MachineConfigFactory.cpp | 15 +- .../details/factory/MachineConfigFactory.hpp | 13 +- .../factory/MachineConfigFactory_new.cpp | 109 ++++ .../details/factory/StaticConfig.hpp | 3 + .../src/daemon/src/common/BUILD | 8 + .../src/common/alive_interface_path.hpp | 36 ++ .../src/daemon/src/configuration/BUILD | 36 ++ .../src/daemon/src/configuration/Readme.md | 13 + .../configuration/configuration_adapter.cpp | 462 ++++++++++++++ .../configuration/configuration_adapter.hpp | 155 +++++ .../configuration_adapter_UT.cpp | 567 ++++++++++++++++++ score/launch_manager/src/daemon/src/main.cpp | 48 +- .../daemon/src/process_group_manager/BUILD | 15 +- .../src/process_group_manager/details/BUILD | 30 +- .../process_group_manager/details/graph.cpp | 7 +- .../process_group_manager/details/graph.hpp | 1 - .../details/process_group_manager.cpp | 46 +- .../details/process_info_node.cpp | 4 +- .../details/process_info_node.hpp | 5 + .../process_group_manager.hpp | 31 +- scripts/config_mapping/config.bzl | 117 +++- scripts/config_mapping/lifecycle_config.py | 226 ++++++- .../complex_monitoring/complex_monitoring.py | 6 + .../crash_on_startup/crash_on_startup.py | 6 + .../test_incorrect_config_non_reporting.py | 7 + .../process_complex_rep_failure.py | 6 + .../process_crash_monitoring.py | 6 + .../process_fd_leak/process_fd_leak.py | 6 + .../process_launch_args.py | 6 + .../process_simple_rep_failure.py | 6 + .../process_wrong_binary_failure.py | 6 + tests/integration/smoke/smoke.py | 7 + .../switch_run_target/switch_run_target.py | 7 + 47 files changed, 2522 insertions(+), 125 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory_new.cpp create mode 100644 score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory_new.cpp create mode 100644 score/launch_manager/src/daemon/src/common/alive_interface_path.hpp create mode 100644 score/launch_manager/src/daemon/src/configuration/Readme.md create mode 100644 score/launch_manager/src/daemon/src/configuration/configuration_adapter.cpp create mode 100644 score/launch_manager/src/daemon/src/configuration/configuration_adapter.hpp create mode 100644 score/launch_manager/src/daemon/src/configuration/configuration_adapter_UT.cpp diff --git a/config/BUILD b/config/BUILD index ae6bb3eca..bb03fba94 100644 --- a/config/BUILD +++ b/config/BUILD @@ -68,3 +68,17 @@ config_setting( }, visibility = ["//visibility:public"], ) + +bool_flag( + name = "use_new_configuration", + build_setting_default = False, + visibility = ["//:__subpackages__"], +) + +config_setting( + name = "lm_use_new_configuration", + flag_values = { + ":use_new_configuration": "True", + }, + visibility = ["//:__subpackages__"], +) diff --git a/examples/demo_verification/test_examples.py b/examples/demo_verification/test_examples.py index ea617e149..70bdc7f7b 100644 --- a/examples/demo_verification/test_examples.py +++ b/examples/demo_verification/test_examples.py @@ -74,9 +74,16 @@ def test_examples(target, setup_test, remote_test_dir): """ lm_path = str(remote_test_dir / "launch_manager") lmcontrol_path = str(remote_test_dir / "lmcontrol") + new_config_path = str(remote_test_dir / "etc/lifecycle_demo_test.bin") _step("Starting launch manager (Startup)") - lm_proc = target.execute_async(lm_path, cwd=str(remote_test_dir)) + + # launch manger will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + lm_proc = target.execute_async( + lm_path, cwd=str(remote_test_dir), args=["-c", new_config_path] + ) time.sleep(1.0) assert lm_proc.is_running(), "Launch manager exited unexpectedly during Startup" diff --git a/score/launch_manager/src/daemon/BUILD b/score/launch_manager/src/daemon/BUILD index 3dd29afdc..c066fc544 100644 --- a/score/launch_manager/src/daemon/BUILD +++ b/score/launch_manager/src/daemon/BUILD @@ -15,6 +15,10 @@ load("@rules_cc//cc:defs.bzl", "cc_binary") cc_binary( name = "launch_manager", srcs = ["src/main.cpp"], + defines = select({ + "//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"], + "//conditions:default": [], + }), linkopts = select({ "@platforms//os:qnx": ["-lsecpol"], "@platforms//os:linux": ["-lpthread"], @@ -28,5 +32,10 @@ cc_binary( "//score/launch_manager/src/daemon/src/process_group_manager:alive_monitor_thread", "//score/launch_manager/src/daemon/src/process_state_client:process_state_notifier", "//score/launch_manager/src/daemon/src/recovery_client", - ], + ] + select({ + "//config:lm_use_new_configuration": [ + "//score/launch_manager/src/daemon/src/configuration:flatbuffer_config_loader", + ], + "//conditions:default": [], + }), ) diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/AliveMonitorImpl.cpp b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/AliveMonitorImpl.cpp index 2ddc8a2a8..d13c256c4 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/AliveMonitorImpl.cpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/AliveMonitorImpl.cpp @@ -30,28 +30,46 @@ namespace saf namespace daemon { -AliveMonitorImpl::AliveMonitorImpl(std::shared_ptr recovery_client, - std::unique_ptr watchdog, - std::unique_ptr process_state_receiver) +#ifdef USE_NEW_CONFIGURATION +AliveMonitorImpl::AliveMonitorImpl(SptrIRecoveryClient recovery_client, + UptrIWatchdogIf watchdog, + UptrIProcessStateReceiver process_state_receiver, + const Config& config) + : m_recovery_client(recovery_client), + m_watchdog(std::move(watchdog)), + m_logger{score::lcm::saf::logging::PhmLogger::getLogger(score::lcm::saf::logging::PhmLogger::EContext::factory)}, + m_process_state_receiver{std::move(process_state_receiver)}, + m_config(config) +{ +} +#else +AliveMonitorImpl::AliveMonitorImpl(SptrIRecoveryClient recovery_client, + UptrIWatchdogIf watchdog, + UptrIProcessStateReceiver process_state_receiver) : m_recovery_client(recovery_client), m_watchdog(std::move(watchdog)), m_logger{score::lcm::saf::logging::PhmLogger::getLogger(score::lcm::saf::logging::PhmLogger::EContext::factory)}, m_process_state_receiver{std::move(process_state_receiver)} { } +#endif EInitCode AliveMonitorImpl::init() noexcept { - score::lcm::saf::daemon::EInitCode initResult{score::lcm::saf::daemon::EInitCode::kGeneralError}; + EInitCode initResult{EInitCode::kGeneralError}; try { m_osClock.startMeasurement(); - m_daemon = std::make_unique( - m_osClock, m_logger, std::move(m_watchdog), std::move(m_process_state_receiver)); + m_daemon = std::make_unique(m_osClock, m_logger, std::move(m_watchdog), + std::move(m_process_state_receiver)); + #ifdef USE_NEW_CONFIGURATION + initResult = m_daemon->init(m_recovery_client, m_config); + #else initResult = m_daemon->init(m_recovery_client); + #endif - if (initResult == score::lcm::saf::daemon::EInitCode::kNoError) + if (initResult == EInitCode::kNoError) { const long ms{m_osClock.endMeasurement()}; m_logger.LogDebug() << "AliveMonitor: Initialization took " << ms << " ms"; diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/AliveMonitorImpl.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/AliveMonitorImpl.hpp index cf40feaa2..f1a46055c 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/AliveMonitorImpl.hpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/AliveMonitorImpl.hpp @@ -17,6 +17,9 @@ #include #include "score/mw/launch_manager/alive_monitor/details/daemon/IAliveMonitor.hpp" +#ifdef USE_NEW_CONFIGURATION +#include "score/mw/launch_manager/configuration/config.hpp" +#endif namespace score { namespace lcm { @@ -31,21 +34,43 @@ class IWatchdogIf; namespace daemon { +using SptrIRecoveryClient = std::shared_ptr; +using UptrIWatchdogIf = std::unique_ptr; +using UptrIProcessStateReceiver = std::unique_ptr; +using Logger = score::lcm::saf::logging::PhmLogger; +using UptrPhmDaemon = std::unique_ptr; +using OsClock = score::lcm::saf::timers::OsClockInterface; +#ifdef USE_NEW_CONFIGURATION +using Config = score::mw::launch_manager::configuration::Config; +#endif + class AliveMonitorImpl : public IAliveMonitor { public: - AliveMonitorImpl(std::shared_ptr recovery_client, std::unique_ptr watchdog, std::unique_ptr process_state_receiver); +#ifdef USE_NEW_CONFIGURATION + AliveMonitorImpl(SptrIRecoveryClient recovery_client, + UptrIWatchdogIf watchdog, + UptrIProcessStateReceiver process_state_receiver, + const Config& config); +#else + AliveMonitorImpl(SptrIRecoveryClient recovery_client, + UptrIWatchdogIf watchdog, + UptrIProcessStateReceiver process_state_receiver); +#endif EInitCode init() noexcept override; bool run(std::atomic_bool& cancel_thread) noexcept override; private: - std::shared_ptr m_recovery_client{nullptr}; - std::unique_ptr m_watchdog{nullptr}; - score::lcm::saf::logging::PhmLogger& m_logger; - std::unique_ptr m_daemon{nullptr}; - score::lcm::saf::timers::OsClockInterface m_osClock{}; - std::unique_ptr m_process_state_receiver; + SptrIRecoveryClient m_recovery_client{nullptr}; + UptrIWatchdogIf m_watchdog{nullptr}; + Logger& m_logger; + UptrPhmDaemon m_daemon{nullptr}; + OsClock m_osClock{}; + UptrIProcessStateReceiver m_process_state_receiver; +#ifdef USE_NEW_CONFIGURATION + const Config& m_config; +#endif }; } // namespace daemon diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/BUILD b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/BUILD index 3f77a1649..c2688f53e 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/BUILD +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/BUILD @@ -24,6 +24,10 @@ cc_library( name = "sw_cluster_handler", srcs = ["SwClusterHandler.cpp"], hdrs = ["SwClusterHandler.hpp"], + defines = select({ + "//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"], + "//conditions:default": [], + }), include_prefix = "score/mw/launch_manager/alive_monitor/details/daemon", strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/daemon", visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], @@ -38,13 +42,22 @@ cc_library( "//score/launch_manager/src/daemon/src/alive_monitor/details/logging:phm_logging", "//score/launch_manager/src/daemon/src/alive_monitor/details/supervision:alive", "//score/launch_manager/src/daemon/src/alive_monitor/details/timers:timers_os_clock", - ], + ] + select({ + "//config:lm_use_new_configuration": [ + "//score/launch_manager/src/daemon/src/configuration:config", + ], + "//conditions:default": [], + }), ) cc_library( name = "phm_daemon", srcs = ["PhmDaemon.cpp"], hdrs = ["PhmDaemon.hpp"], + defines = select({ + "//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"], + "//conditions:default": [], + }), include_prefix = "score/mw/launch_manager/alive_monitor/details/daemon", strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/daemon", visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], @@ -63,7 +76,12 @@ cc_library( "//score/launch_manager/src/daemon/src/alive_monitor/details/timers:timers_os_clock", "//score/launch_manager/src/daemon/src/alive_monitor/details/watchdog:i_watchdog_if", "//score/launch_manager/src/lifecycle_client", - ], + ] + select({ + "//config:lm_use_new_configuration": [ + "//score/launch_manager/src/daemon/src/configuration:config", + ], + "//conditions:default": [], + }), ) cc_library( @@ -79,6 +97,10 @@ cc_library( name = "health_monitor_impl", srcs = ["AliveMonitorImpl.cpp"], hdrs = ["AliveMonitorImpl.hpp"], + defines = select({ + "//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"], + "//conditions:default": [], + }), include_prefix = "score/mw/launch_manager/alive_monitor/details/daemon", strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/daemon", visibility = ["//score/launch_manager/src/daemon:__subpackages__"], diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/PhmDaemon.cpp b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/PhmDaemon.cpp index bc8fef0e7..af5c43288 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/PhmDaemon.cpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/PhmDaemon.cpp @@ -31,10 +31,10 @@ namespace daemon true_no_defect) */ /* RULECHECKER_comment(0, 4, check_incomplete_data_member_construction, "Default constructor is used for\ processStateReader.", true_no_defect) */ -PhmDaemon::PhmDaemon(score::lcm::saf::timers::OsClockInterface& f_osClock, - logging::PhmLogger& f_logger_r, - std::unique_ptr f_watchdog, - std::unique_ptr f_process_state_receiver) +PhmDaemon::PhmDaemon(OsClock& f_osClock, + Logger& f_logger_r, + std::unique_ptr f_watchdog, + std::unique_ptr f_process_state_receiver) : osClock{f_osClock}, cycleTimer{&osClock}, logger_r{f_logger_r}, @@ -49,7 +49,7 @@ void PhmDaemon::performCyclicTriggers(void) { bool isCriticalFailure{false}; - timers::NanoSecondType syncTimestamp{timers::OsClock::getMonotonicSystemClock()}; + NanoSecondType syncTimestamp{timers::OsClock::getMonotonicSystemClock()}; if (syncTimestamp == 0U) { // No valid time value, use max value for synchronization @@ -82,8 +82,11 @@ void PhmDaemon::performCyclicTriggers(void) } } -bool PhmDaemon::construct(const factory::MachineConfigFactory::SupervisionBufferConfig& f_bufferConfig_r) noexcept( - false) +#ifdef USE_NEW_CONFIGURATION +bool PhmDaemon::construct(const Config& config, const SupervisionBufferConfig& f_bufferConfig_r) noexcept(false) +#else +bool PhmDaemon::construct(const SupervisionBufferConfig& f_bufferConfig_r) noexcept(false) +#endif { bool isSuccess{true}; @@ -108,7 +111,11 @@ bool PhmDaemon::construct(const factory::MachineConfigFactory::SupervisionBuffer for (auto strSwClusterName : listSwClustersPhm.value()) { swClusterHandlers.emplace_back(strSwClusterName); +#ifdef USE_NEW_CONFIGURATION + isSuccess = swClusterHandlers.back().constructWorkers(config, recoveryClient, processStateReader, f_bufferConfig_r); +#else isSuccess = swClusterHandlers.back().constructWorkers(recoveryClient, processStateReader, f_bufferConfig_r); +#endif if (!isSuccess) { logger_r.LogError() << "Phm Daemon: failed to create worker objects for swclusterhandler:" diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/PhmDaemon.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/PhmDaemon.hpp index c453edf6f..a7e95eb21 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/PhmDaemon.hpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/PhmDaemon.hpp @@ -28,6 +28,9 @@ #include "score/mw/launch_manager/alive_monitor/details/timers/CycleTimeValidator.hpp" #include "score/mw/launch_manager/alive_monitor/details/timers/CycleTimer.hpp" #include "score/mw/launch_manager/alive_monitor/details/watchdog/IWatchdogIf.hpp" +#ifdef USE_NEW_CONFIGURATION +#include "score/mw/launch_manager/configuration/config.hpp" +#endif namespace score { namespace lcm @@ -58,6 +61,20 @@ enum class EInitCode : std::int8_t class PhmDaemon { public: + using OsClock = score::lcm::saf::timers::OsClockInterface; + using Logger = logging::PhmLogger; + using Watchdog = watchdog::IWatchdogIf; + using ProcessStateReceiver = score::lcm::IProcessStateReceiver; + using RecoveryClient = score::lcm::IRecoveryClient; + using MachineConfigFactory = factory::MachineConfigFactory; + using SupervisionBufferConfig = MachineConfigFactory::SupervisionBufferConfig; + using CycleTimer = score::lcm::saf::timers::CycleTimer; + using CycleTimeValidator = score::lcm::saf::timers::CycleTimeValidator; + using NanoSecondType = score::lcm::saf::timers::NanoSecondType; + using ProcessStateReader = score::lcm::saf::ifexm::ProcessStateReader; +#ifdef USE_NEW_CONFIGURATION + using Config = score::mw::launch_manager::configuration::Config; +#endif /* RULECHECKER_comment(0, 4, check_expensive_to_copy_in_parameter, "f_supervisionErrorInfo name is passed by value\ as same as generated function", true_no_defect) */ @@ -68,8 +85,8 @@ class PhmDaemon /// @param[in] f_process_state_receiver process state receiver implementation (dependency injection possible in tests) /* RULECHECKER_comment(3,1, check_expensive_to_copy_in_parameter, "Move only types cannot be passed by const ref", true_no_defect) */ - PhmDaemon(score::lcm::saf::timers::OsClockInterface& f_osClock, logging::PhmLogger& f_logger_r, - std::unique_ptr f_watchdog, std::unique_ptr f_process_state_receiver); + PhmDaemon(OsClock& f_osClock, Logger& f_logger_r, std::unique_ptr f_watchdog, + std::unique_ptr f_process_state_receiver); /* RULECHECKER_comment(0, 4, check_min_instructions, "Default destructor is not provided\ a function body", true_no_defect) */ @@ -89,24 +106,38 @@ class PhmDaemon /// (Constructing the workers, adjusting the cycle time, initialization of fixed step timer) /// @param[in] recovery_client Shared pointer to recovery client /// @return See EInitCode definition - EInitCode init(std::shared_ptr recovery_client) noexcept(false) +#ifdef USE_NEW_CONFIGURATION + EInitCode init(std::shared_ptr recovery_client, const Config& config) noexcept(false) { recoveryClient = recovery_client; - factory::MachineConfigFactory machineConfig{}; + MachineConfigFactory machineConfig{}; + if (!machineConfig.init(config)) + { + return EInitCode::kMachineConfigInitFailed; + } + + if (!construct(config, machineConfig.getSupervisionBufferConfig())) +#else + EInitCode init(std::shared_ptr recovery_client) noexcept(false) + { + recoveryClient = recovery_client; + + MachineConfigFactory machineConfig{}; if (!machineConfig.init()) { return EInitCode::kMachineConfigInitFailed; } if (!construct(machineConfig.getSupervisionBufferConfig())) +#endif { return EInitCode::kConstructFlatCfgFactoryFailed; } int64_t cycleTimeModified{static_cast(machineConfig.getCycleTimeInNs())}; cycleTimeModified = - score::lcm::saf::timers::CycleTimeValidator::adjustCycleTimeOnClockAccuracy(cycleTimeModified, osClock); + CycleTimeValidator::adjustCycleTimeOnClockAccuracy(cycleTimeModified, osClock); const int64_t timerInit{cycleTimer.init(cycleTimeModified)}; if (timerInit > 0) @@ -114,9 +145,7 @@ class PhmDaemon logger_r.LogInfo() << "Phm Daemon: The (configured) periodicity in [ns] is set to:" << static_cast(cycleTimeModified); logger_r.LogDebug() << "Phm Daemon: The accuracy of the monotonic system clock in [ns] is:" - << static_cast( - score::lcm::saf::timers::CycleTimeValidator::getMonotonicClockAccuracy( - osClock)); + << static_cast(CycleTimeValidator::getMonotonicClockAccuracy(osClock)); } else { @@ -162,7 +191,7 @@ class PhmDaemon template bool startCyclicExec(const TerminationSignalPredType& f_terminateCond) noexcept { - timers::NanoSecondType startTimestamp{cycleTimer.start()}; + NanoSecondType startTimestamp{cycleTimer.start()}; if (startTimestamp == 0U) { logger_r.LogError() << "Phm Daemon: Failed to get initial timestamp"; @@ -190,7 +219,7 @@ class PhmDaemon logger_r.LogInfo() << "Phm Daemon: Sleep was interrupted by termination signal"; break; } - else if (sleepResult == timers::CycleTimer::kDeadlineAlreadyOver) + else if (sleepResult == CycleTimer::kDeadlineAlreadyOver) { logger_r.LogDebug() << "Phm Daemon: Phm cycle took" << (static_cast(nsOverDeadline) / 1000000.0 /*ns per ms*/) @@ -217,32 +246,36 @@ class PhmDaemon /// @details Create the SwclusterHandler objects and the workers for the SwclusterHandler /// @param[in] f_bufferConfig_r The buffer configuration used for worker construction /// @return bool true if workers creation succeeded, false otherwise - bool construct(const factory::MachineConfigFactory::SupervisionBufferConfig& f_bufferConfig_r) noexcept(false); +#ifdef USE_NEW_CONFIGURATION + bool construct(const Config& config, const SupervisionBufferConfig& f_bufferConfig_r) noexcept(false); +#else + bool construct(const SupervisionBufferConfig& f_bufferConfig_r) noexcept(false); +#endif /// @brief Perform cyclic execution of Phm daemon /// @details Perform cyclic execution of Phm daemon functionalities, for e.g., evaluation of supervisions. void performCyclicTriggers(void); /// @brief System clock interface to access the monotonic clock for sleep - score::lcm::saf::timers::OsClockInterface& osClock; + OsClock& osClock; /// @brief For fixed time-step execution during the cyclic execution - score::lcm::saf::timers::CycleTimer cycleTimer; + CycleTimer cycleTimer; /// @brief Logging entity for warnings, errors used in init() phase and the cyclic() phase - logging::PhmLogger& logger_r; + Logger& logger_r; /// @brief Recovery interface to Launch Manager - std::shared_ptr recoveryClient; + std::shared_ptr recoveryClient; /// @brief Vector of SwCluster handler std::vector swClusterHandlers; /// @brief Process State Reader for PHM daemon - score::lcm::saf::ifexm::ProcessStateReader processStateReader; + ProcessStateReader processStateReader; /// @brief Connection to watchdog devices - std::unique_ptr watchdog; + std::unique_ptr watchdog; }; } // namespace daemon diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/SwClusterHandler.cpp b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/SwClusterHandler.cpp index 574b0e53c..d183b7190 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/SwClusterHandler.cpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/SwClusterHandler.cpp @@ -47,6 +47,9 @@ SwClusterHandler::~SwClusterHandler() = default; /* RULECHECKER_comment(0, 3, check_max_cyclomatic_complexity, "Max cyclomatic complexity violation\ is tolerated for this function. ", true_no_defect) */ bool SwClusterHandler::constructWorkers( +#ifdef USE_NEW_CONFIGURATION + const score::mw::launch_manager::configuration::Config& config, +#endif std::shared_ptr f_recoveryClient_r, ifexm::ProcessStateReader& f_processStateReader_r, const factory::MachineConfigFactory::SupervisionBufferConfig& f_bufferConfig_r) noexcept(false) @@ -54,8 +57,12 @@ bool SwClusterHandler::constructWorkers( bool isSuccess{false}; factory::FlatCfgFactory flatCfgFactory{f_bufferConfig_r}; +#ifdef USE_NEW_CONFIGURATION + isSuccess = flatCfgFactory.init(config); +#else const std::string filename = "hm_demo.bin"; isSuccess = flatCfgFactory.init(filename); +#endif if (isSuccess) { logger_r.LogDebug() << "Software Cluster Handler starts constructing workers:" << f_swClusterName; diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/SwClusterHandler.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/SwClusterHandler.hpp index 7512d6ee9..8306d697f 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/SwClusterHandler.hpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/daemon/SwClusterHandler.hpp @@ -20,6 +20,9 @@ #include "score/mw/launch_manager/alive_monitor/details/ifexm/ProcessStateReader.hpp" #include "score/mw/launch_manager/alive_monitor/details/logging/PhmLogger.hpp" #include "score/mw/launch_manager/alive_monitor/details/timers/Timers_OsClock.hpp" +#ifdef USE_NEW_CONFIGURATION +#include "score/mw/launch_manager/configuration/config.hpp" +#endif #include #include @@ -92,6 +95,9 @@ class SwClusterHandler /// @param [in] f_bufferConfig_r Configuration settings for constructing workers /// @return Construction is successful (true), otherwise failure (false) bool constructWorkers( +#ifdef USE_NEW_CONFIGURATION + const score::mw::launch_manager::configuration::Config& config, +#endif std::shared_ptr f_recoveryClient_r, ifexm::ProcessStateReader& f_processStateReader_r, const factory::MachineConfigFactory::SupervisionBufferConfig& f_bufferConfig_r) noexcept(false); diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/BUILD b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/BUILD index 82187ef53..286ad86ad 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/BUILD +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/BUILD @@ -38,8 +38,15 @@ cc_library( cc_library( name = "machine_config_factory", - srcs = ["MachineConfigFactory.cpp"], + srcs = select({ + "//config:lm_use_new_configuration": ["MachineConfigFactory_new.cpp"], + "//conditions:default": ["MachineConfigFactory.cpp"], + }), hdrs = ["MachineConfigFactory.hpp"], + defines = select({ + "//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"], + "//conditions:default": [], + }), include_prefix = "score/mw/launch_manager/alive_monitor/details/factory", strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/factory", visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], @@ -53,6 +60,11 @@ cc_library( "@score_baselibs//score/flatbuffers:flatbufferscpp", "@score_baselibs//score/language/futurecpp", ] + select({ + "//config:lm_use_new_configuration": [ + "//score/launch_manager/src/daemon/src/configuration:config", + ], + "//conditions:default": [], + }) + select({ "@platforms//os:qnx": [], "@platforms//os:linux": ["//externals/acl"], }), @@ -60,8 +72,15 @@ cc_library( cc_library( name = "flat_cfg_factory", - srcs = ["FlatCfgFactory.cpp"], + srcs = select({ + "//config:lm_use_new_configuration": ["FlatCfgFactory_new.cpp"], + "//conditions:default": ["FlatCfgFactory.cpp"], + }), hdrs = ["FlatCfgFactory.hpp"], + defines = select({ + "//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"], + "//conditions:default": [], + }), include_prefix = "score/mw/launch_manager/alive_monitor/details/factory", strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/factory", visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], @@ -82,6 +101,12 @@ cc_library( "//score/launch_manager/src/daemon/src/common:identifier_hash", "@score_baselibs//score/flatbuffers:flatbufferscpp", ] + select({ + "//config:lm_use_new_configuration": [ + "//score/launch_manager/src/daemon/src/common:alive_interface_path", + "//score/launch_manager/src/daemon/src/configuration:config", + ], + "//conditions:default": [], + }) + select({ "@platforms//os:qnx": [], "@platforms//os:linux": ["//externals/acl"], }), diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory.cpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory.cpp index bc4832003..13ae941a0 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory.cpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory.cpp @@ -38,6 +38,11 @@ namespace saf namespace factory { +using BufferConfig = MachineConfigFactory::SupervisionBufferConfig; +using RecoveryClient = score::lcm::IRecoveryClient; +using NanoSecondType = saf::timers::NanoSecondType; +using IdentifierHash = score::lcm::IdentifierHash; + namespace { /// @brief Prefix for all log messages @@ -67,7 +72,7 @@ std::unique_ptr read_flatbuffer_file(const std::string& f_filename_r) /* RULECHECKER_comment(0, 7, check_incomplete_data_member_construction, "Members processDataBase is not \ required to be initialized using member initializer list.", true_no_defect) */ -FlatCfgFactory::FlatCfgFactory(const factory::MachineConfigFactory::SupervisionBufferConfig& f_bufferConfig_r) +FlatCfgFactory::FlatCfgFactory(const BufferConfig& f_bufferConfig_r) : IPhmFactory(), bufferConfig_r(f_bufferConfig_r), flatBuffer_p(nullptr), @@ -364,7 +369,7 @@ bool FlatCfgFactory::createSupervisionCheckpoints(std::vector& f_alive_r, std::vector& f_checkpoints_r, std::vector& f_processStates_r, - std::shared_ptr f_recoveryClient_r) + std::shared_ptr f_recoveryClient_r) { bool isSuccess{true}; @@ -381,7 +386,7 @@ bool FlatCfgFactory::createAliveSupervisions(std::vector& f_ { // Collect Alive Supervision configuration const char* nameCfgAlive_p{hmAliveSupervision_p->ruleContextKey()->c_str()}; - saf::timers::NanoSecondType aliveReferenceCycleCfg{ + NanoSecondType aliveReferenceCycleCfg{ timers::TimeConversion::convertMilliSecToNanoSec(hmAliveSupervision_p->aliveReferenceCycle())}; uint32_t minAliveIndicationsCfg{hmAliveSupervision_p->minAliveIndications()}; uint32_t maxAliveIndicationsCfg{hmAliveSupervision_p->maxAliveIndications()}; @@ -409,7 +414,7 @@ bool FlatCfgFactory::createAliveSupervisions(std::vector& f_ // coverity[cert_exp34_c_violation] PHM.ecucfgdsl Process.identifier MANDATORY // coverity[dereference] PHM.ecucfgdsl Process.identifier MANDATORY aliveSupCfg.processIdentifier = - score::lcm::IdentifierHash{flatBuffer_p->process()->Get(processIndex)->identifier()->str()}; + IdentifierHash{flatBuffer_p->process()->Get(processIndex)->identifier()->str()}; f_alive_r.emplace_back(aliveSupCfg); diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory.hpp index d9ddd870a..a2ffea323 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory.hpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory.hpp @@ -23,8 +23,12 @@ #include "score/mw/launch_manager/alive_monitor/details/factory/IPhmFactory.hpp" #include "score/mw/launch_manager/alive_monitor/details/factory/MachineConfigFactory.hpp" #include "score/mw/launch_manager/alive_monitor/details/ifexm/ProcessStateReader.hpp" +#ifdef USE_NEW_CONFIGURATION +#include "score/mw/launch_manager/configuration/config.hpp" +#else #include "score/mw/launch_manager/alive_monitor/config/hm_flatcfg_generated.h" #include "flatbuffers/flatbuffers.h" +#endif namespace score { namespace lcm { @@ -75,8 +79,11 @@ class FlatCfgFactory : public IPhmFactory /// @param [inout] f_flatCfgPhm_r FlatCfg configuration for PHM /// @param [in] f_nameSwCluster_r Software Cluster name which for which workers shall be constructed /// @return Initialization is successful (true), otherwise failure (false) - // bool init(flatcfg::FlatCfg& f_flatCfgPhm_r, const std::string& f_nameSwCluster_r); +#ifdef USE_NEW_CONFIGURATION + bool init(const score::mw::launch_manager::configuration::Config& config); +#else bool init(const std::string& f_filename_r); +#endif /// @brief Refer to the description of the base class (IPhmFactory) bool createProcessStates(std::vector& f_processStates_r, @@ -104,9 +111,16 @@ class FlatCfgFactory : public IPhmFactory private: /// @brief Get process id based on ASR path of process +#ifdef USE_NEW_CONFIGURATION + /// @param[in] comp Pointer to component configuration + /// @return process id + static score::lcm::IdentifierHash getProcessId( + const score::mw::launch_manager::configuration::ComponentConfig* comp) noexcept(true); +#else /// @param[in] f_processPath_r ASR path of process /// @return process id or nullopt in case of an error std::optional getProcessId(const std::string& f_processPath_r) noexcept(true); +#endif /// @brief Create IPC Channel with uid-based access permission /// @details Only the given uid will ge granted r/w access, no group will be granted access @@ -121,12 +135,17 @@ class FlatCfgFactory : public IPhmFactory /// @brief The buffer configuration for constructing supervision objects const factory::MachineConfigFactory::SupervisionBufferConfig& bufferConfig_r; +#ifdef USE_NEW_CONFIGURATION + const score::mw::launch_manager::configuration::Config* config_; + std::vector supervised_components_; + std::vector alive_cfg_names_; +#else /// Pointer to PHM Flat Buffer for given Software Cluster /// Raw pointer is used here because the memory is deallocated by FlatBuffer. const HMFlatBuffer::HMEcuCfg* flatBuffer_p; - /// Pointer for loaded Software Cluster std::unique_ptr loadBuffer_p; +#endif /// Logger object for logging messages logging::PhmLogger& logger_r; diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory_new.cpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory_new.cpp new file mode 100644 index 000000000..125a05f00 --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/FlatCfgFactory_new.cpp @@ -0,0 +1,334 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +#include "score/mw/launch_manager/alive_monitor/details/factory/FlatCfgFactory.hpp" + +#include +#include +#include + +#include "score/mw/launch_manager/alive_monitor/details/factory/IPhmFactory.hpp" +#include "score/mw/launch_manager/alive_monitor/details/factory/StaticConfig.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifappl/Checkpoint.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifappl/MonitorIfDaemon.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifexm/ProcessState.hpp" +#include "score/mw/launch_manager/alive_monitor/details/logging/PhmLogger.hpp" +#include "score/mw/launch_manager/alive_monitor/details/supervision/Alive.hpp" +#include "score/mw/launch_manager/alive_monitor/details/supervision/SupervisionCfg.hpp" +#include "score/mw/launch_manager/alive_monitor/details/timers/TimeConversion.hpp" +#include "score/mw/launch_manager/alive_monitor/details/timers/Timers_OsClock.hpp" +#include "score/mw/launch_manager/common/alive_interface_path.hpp" +#include "score/mw/launch_manager/common/identifier_hash.hpp" + +namespace score { +namespace lcm { +namespace saf { +namespace factory { + +using BufferConfig = MachineConfigFactory::SupervisionBufferConfig; +using Config = score::mw::launch_manager::configuration::Config; +using ComponentConfig = score::mw::launch_manager::configuration::ComponentConfig; +using ApplicationType = score::mw::launch_manager::configuration::ApplicationType; +using RecoveryClient = score::lcm::IRecoveryClient; +using NanoSecondType = saf::timers::NanoSecondType; +using IdentifierHash = score::lcm::IdentifierHash; + +namespace { + +bool isSupervisedType(ApplicationType app_type) { + return app_type == ApplicationType::ReportingAndSupervised || + app_type == ApplicationType::StateManager; +} + +} // namespace + +FlatCfgFactory::FlatCfgFactory( + const BufferConfig &f_bufferConfig_r) + : IPhmFactory(), bufferConfig_r(f_bufferConfig_r), config_(nullptr), + logger_r(logging::PhmLogger::getLogger( + logging::PhmLogger::EContext::factory)) {} + +bool FlatCfgFactory::init(const Config &config) { + config_ = &config; + + supervised_components_.clear(); + for (const auto &comp : config_->components()) { + if (isSupervisedType( + comp.component_properties.application_profile.application_type)) { + supervised_components_.push_back(&comp); + } + } + + return true; +} + +bool FlatCfgFactory::createProcessStates( + std::vector &f_processStates_r, + ifexm::ProcessStateReader &f_processStateReader_r) { + bool isSuccess{true}; + + try { + f_processStates_r.reserve(supervised_components_.size()); + for (const auto *comp : supervised_components_) { + ifexm::ProcessCfg processCfg{}; + processCfg.processShortName = std::string_view(comp->name); + + const auto processId = getProcessId(comp); + processCfg.processId = processId.data(); + + f_processStates_r.emplace_back(processCfg); + isSuccess = f_processStateReader_r.registerProcessState( + f_processStates_r.back(), processCfg.processId); + if (!isSuccess) { + break; + } + + logger_r.LogDebug() << "Successfully created Process States:" + << comp->name; + } + } catch (const std::exception &f_exception_r) { + isSuccess = false; + logger_r.LogError() << "Could not create Process States due to exception:" + << std::string_view{f_exception_r.what()}; + } + + if (isSuccess) { + logger_r.LogDebug() << "Number of constructed Process States:" + << static_cast(f_processStates_r.size()); + } else { + for (auto &processState_r : f_processStates_r) { + f_processStateReader_r.deregisterProcessState( + processState_r.getProcessId()); + } + f_processStates_r.clear(); + logger_r.LogError() << "Could not create all necessary Process States."; + } + + return isSuccess; +} + +bool FlatCfgFactory::initIpcServerWithUidBasedAccess( + ifappl::CheckpointIpcServer &f_ipcServer_r, const std::string &f_ipcPath_r, + const std::int32_t f_uid) noexcept(false) { + constexpr mode_t kOwnerReadWrite{384U}; // 0600 in octal + if (f_ipcServer_r.init(f_ipcPath_r, kOwnerReadWrite) != + ifappl::CheckpointIpcServer::EIpcInitResult::kOk) { + return false; + } + + const uid_t uid = static_cast(f_uid); + if (!f_ipcServer_r.setAccessRights(uid)) { + logger_r.LogError() << "Could not set ACL permissions (r/w for uid" << uid + << ") for Monitor interface IPC with path:" + << f_ipcPath_r; + return false; + } + return true; +} + +bool FlatCfgFactory::createAliveIfIpcs( + std::vector &f_interfaceIpcs_r) { + bool isSuccess{true}; + try { + f_interfaceIpcs_r.reserve(supervised_components_.size()); + + for (const auto *comp : supervised_components_) { + const std::string pathInterface = + score::lcm::internal::aliveInterfacePath(comp->name); + f_interfaceIpcs_r.emplace_back(); + const std::int32_t configuredUid = + static_cast(comp->deployment_config.sandbox.uid); + isSuccess = initIpcServerWithUidBasedAccess(f_interfaceIpcs_r.back(), + pathInterface, configuredUid); + + if (isSuccess) { + logger_r.LogDebug() + << "Successfully created Monitor interface IPC with path:" + << pathInterface; + } else { + logger_r.LogError() + << "Could not create Monitor interface IPC with path:" + << pathInterface; + break; + } + } + } catch (const std::exception &f_exception_r) { + isSuccess = false; + logger_r.LogError() + << "Could not create Monitor interface IPC due to exception:" + << std::string_view{f_exception_r.what()}; + } + + if (isSuccess) { + logger_r.LogDebug() << "Number of constructed Monitor interface IPCs:" + << static_cast(f_interfaceIpcs_r.size()); + } else { + f_interfaceIpcs_r.clear(); + logger_r.LogError() + << "Could not create all necessary Monitor interface IPCs."; + } + + return isSuccess; +} + +bool FlatCfgFactory::createAliveIf( + std::vector &f_interfaces_r, + std::vector &f_interfaceIpcs_r, + std::vector &f_processStates_r) { + bool isSuccess{true}; + try { + f_interfaces_r.reserve(supervised_components_.size()); + for (std::size_t compIndex = 0; compIndex < supervised_components_.size(); + ++compIndex) { + auto &interfaceIpc = f_interfaceIpcs_r.at(compIndex); + f_interfaces_r.emplace_back(interfaceIpc, interfaceIpc.getPath().data()); + f_processStates_r.at(compIndex).attachObserver(f_interfaces_r.back()); + + logger_r.LogDebug() << "Successfully created MonitorInterface:" + << f_interfaces_r.back().getInterfaceName(); + } + + logger_r.LogDebug() << "Number of constructed Monitor interfaces:" + << static_cast(f_interfaces_r.size()); + } catch (const std::exception &f_exception_r) { + isSuccess = false; + f_interfaces_r.clear(); + logger_r.LogError() + << "Could not create all necessary Monitor interfaces due to exception:" + << std::string_view{f_exception_r.what()}; + } + + return isSuccess; +} + +bool FlatCfgFactory::createSupervisionCheckpoints( + std::vector &f_checkpoints_r, + std::vector &f_interfaces_r, + std::vector &f_processStates_r) { + bool isSuccess{true}; + + try { + f_checkpoints_r.reserve(supervised_components_.size()); + + for (size_t idx = 0; idx < supervised_components_.size(); ++idx) { + const auto *comp = supervised_components_[idx]; + const std::string checkpointCfgName = comp->name + "_checkpoint"; + const uint32_t checkpointId = StaticConfig::k_DefaultCheckpointId; + + const ifexm::ProcessState *process_p{&f_processStates_r.at(idx)}; + f_checkpoints_r.emplace_back(checkpointCfgName.c_str(), checkpointId, + process_p); + f_interfaces_r.at(idx).attachCheckpoint(f_checkpoints_r.back()); + + logger_r.LogDebug() << "Successfully created supervision checkpoint:" + << f_checkpoints_r.back().getConfigName(); + } + } catch (const std::exception &f_exception_r) { + isSuccess = false; + logger_r.LogError() + << "Could not create supervision worker objects, due to exception:" + << std::string_view{f_exception_r.what()}; + } + + if (isSuccess) { + logger_r.LogDebug() << "Number of constructed supervision checkpoints:" + << static_cast(f_checkpoints_r.size()); + } else { + f_checkpoints_r.clear(); + logger_r.LogError() + << "Could not create all necessary supervision checkpoints."; + } + + return isSuccess; +} + +bool FlatCfgFactory::createAliveSupervisions( + std::vector &f_alive_r, + std::vector &f_checkpoints_r, + std::vector &f_processStates_r, + std::shared_ptr f_recoveryClient_r) { + bool isSuccess{true}; + + try { + f_alive_r.reserve(supervised_components_.size()); + alive_cfg_names_.clear(); + alive_cfg_names_.reserve(supervised_components_.size()); + + for (size_t idx = 0; idx < supervised_components_.size(); ++idx) { + const auto *comp = supervised_components_[idx]; + const auto &alive_sup = + comp->component_properties.application_profile.alive_supervision; + assert(alive_sup.has_value() && + "Supervised component must have alive_supervision configured"); + + alive_cfg_names_.emplace_back(comp->name + "_alive_supervision"); + NanoSecondType aliveReferenceCycleCfg{ + timers::TimeConversion::convertMilliSecToNanoSec( + static_cast(alive_sup->reporting_cycle_ms))}; + uint32_t minAliveIndicationsCfg = alive_sup->min_indications.value_or(0U); + uint32_t maxAliveIndicationsCfg = alive_sup->max_indications.value_or(0U); + bool isMinCheckDisabledCfg = (minAliveIndicationsCfg == 0U); + bool isMaxCheckDisabledCfg = (maxAliveIndicationsCfg == 0U); + uint32_t failedCyclesToleranceCfg = alive_sup->failed_cycles_tolerance; + + supervision::AliveSupervisionCfg aliveSupCfg{f_checkpoints_r.at(idx)}; + + aliveSupCfg.cfgName_p = alive_cfg_names_.back().c_str(); + aliveSupCfg.aliveReferenceCycle = aliveReferenceCycleCfg; + aliveSupCfg.minAliveIndications = minAliveIndicationsCfg; + aliveSupCfg.maxAliveIndications = maxAliveIndicationsCfg; + aliveSupCfg.isMinCheckDisabled = isMinCheckDisabledCfg; + aliveSupCfg.isMaxCheckDisabled = isMaxCheckDisabledCfg; + aliveSupCfg.failedCyclesTolerance = failedCyclesToleranceCfg; + aliveSupCfg.checkpointBufferSize = + bufferConfig_r.bufferSizeAliveSupervision; + aliveSupCfg.recoveryClient = f_recoveryClient_r; + + aliveSupCfg.processIdentifier = getProcessId(comp); + + f_alive_r.emplace_back(aliveSupCfg); + + f_processStates_r.at(idx).attachObserver(f_alive_r.back()); + + logger_r.LogDebug() + << "Successfully created alive supervision worker object:" + << f_alive_r.back().getConfigName(); + } + } catch (const std::exception &f_exception_r) { + isSuccess = false; + logger_r.LogError() << "Could not create all necessary alive supervision " + "worker objects, due to exception:" + << std::string_view{f_exception_r.what()}; + } + + if (isSuccess) { + logger_r.LogDebug() << "Number of constructed alive supervisions:" + << static_cast(f_alive_r.size()); + } else { + f_alive_r.clear(); + logger_r.LogError() + << "Could not create all necessary alive supervision worker objects"; + } + + return isSuccess; +} + +IdentifierHash FlatCfgFactory::getProcessId( + const ComponentConfig *comp) noexcept(true) { + return IdentifierHash{comp->name}; +} + +} // namespace factory +} // namespace saf +} // namespace lcm +} // namespace score diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory.cpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory.cpp index 910a934ee..fa4a69e44 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory.cpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory.cpp @@ -30,6 +30,10 @@ namespace saf namespace factory { +using HMCoreEcuCfg = HMCOREFlatBuffer::HMCOREEcuCfg; +using DeviceConfigurations = watchdog::IDeviceConfigFactory::DeviceConfigurations; +using NanoSecondType = timers::NanoSecondType; + namespace { /// @brief Prefix for all log messages @@ -95,7 +99,7 @@ bool MachineConfigFactory::init() noexcept(false) return loadHmCoreConfig(flatBuffer_p); } -bool MachineConfigFactory::loadHmCoreConfig(const HMCOREFlatBuffer::HMCOREEcuCfg* f_cfg_r) noexcept(false) +bool MachineConfigFactory::loadHmCoreConfig(const HMCoreEcuCfg* f_cfg_r) noexcept(false) { loadHmSettings(*f_cfg_r); loadWatchdogDevices(*f_cfg_r); @@ -104,7 +108,7 @@ bool MachineConfigFactory::loadHmCoreConfig(const HMCOREFlatBuffer::HMCOREEcuCfg return true; } -void MachineConfigFactory::loadWatchdogDevices(const HMCOREFlatBuffer::HMCOREEcuCfg& f_flatBuffer_r) noexcept(false) +void MachineConfigFactory::loadWatchdogDevices(const HMCoreEcuCfg& f_flatBuffer_r) noexcept(false) { const auto* watchdogs{f_flatBuffer_r.watchdogs()}; if ((watchdogs == nullptr) || (watchdogs->size() == 0U)) @@ -137,7 +141,7 @@ void MachineConfigFactory::loadWatchdogDevices(const HMCOREFlatBuffer::HMCOREEcu } } -void MachineConfigFactory::loadHmSettings(const HMCOREFlatBuffer::HMCOREEcuCfg& f_flatBuffer_r) noexcept(true) +void MachineConfigFactory::loadHmSettings(const HMCoreEcuCfg& f_flatBuffer_r) noexcept(true) { const auto* configContainer{f_flatBuffer_r.config()}; if ((configContainer != nullptr) && (configContainer->size() == 1U)) @@ -163,13 +167,12 @@ void MachineConfigFactory::loadHmSettings(const HMCOREFlatBuffer::HMCOREEcuCfg& } } -std::optional MachineConfigFactory::getDeviceConfigurations() - const +std::optional MachineConfigFactory::getDeviceConfigurations() const { return watchdogConfigs; } -timers::NanoSecondType MachineConfigFactory::getCycleTimeInNs() const noexcept(true) +NanoSecondType MachineConfigFactory::getCycleTimeInNs() const noexcept(true) { return cycleTimeNs; } diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory.hpp index ab4d7b071..67a8a1902 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory.hpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory.hpp @@ -20,12 +20,15 @@ #include "score/mw/launch_manager/alive_monitor/details/logging/PhmLogger.hpp" #include "score/mw/launch_manager/alive_monitor/details/timers/Timers_OsClock.hpp" #include "score/mw/launch_manager/alive_monitor/details/watchdog/IDeviceConfigFactory.hpp" - +#ifdef USE_NEW_CONFIGURATION +#include "score/mw/launch_manager/configuration/config.hpp" +#else namespace HMCOREFlatBuffer { /* RULECHECKER_comment(1:0,1:0, check_non_pod_struct, "External data type form generated flatbuffer code", true_no_defect) */ struct HMCOREEcuCfg; } // namespace PHMCOREFlatBuffer +#endif namespace score { @@ -78,7 +81,11 @@ class MachineConfigFactory : public watchdog::IDeviceConfigFactory /// False, if an invalid machine configuration was provided. /// @note FlatCfg constructor does not define any exception guarantee and may throw a non specified exception /// @throws std::bad_alloc in case of insufficient memory +#ifdef USE_NEW_CONFIGURATION + bool init(const score::mw::launch_manager::configuration::Config& config) noexcept(false); +#else bool init() noexcept(false); +#endif /// @copydoc IDeviceConfigFactory::getDeviceConfigurations() std::optional getDeviceConfigurations() const override; @@ -92,6 +99,7 @@ class MachineConfigFactory : public watchdog::IDeviceConfigFactory const SupervisionBufferConfig& getSupervisionBufferConfig() const noexcept(true); private: +#ifndef USE_NEW_CONFIGURATION /// @brief Loads the hm machine config /// @param [in] f_cfg_r The flatcfg api /// @throws std::bad_alloc for string allocation in case of insufficient memory @@ -105,6 +113,7 @@ class MachineConfigFactory : public watchdog::IDeviceConfigFactory /// @brief Load HM settings from the machine config. I.e. buffer sizes, periodicity, etc. /// @param [in] f_flatBuffer_r The flatcfg buffer void loadHmSettings(const HMCOREFlatBuffer::HMCOREEcuCfg& f_flatBuffer_r) noexcept(true); +#endif /// @brief Log all configuration settings void logConfiguration() noexcept(true); @@ -119,9 +128,11 @@ class MachineConfigFactory : public watchdog::IDeviceConfigFactory /// @brief Configured supervision buffer sizes SupervisionBufferConfig supBufferCfg{}; +#ifndef USE_NEW_CONFIGURATION /// Pointer to HM Flat Buffer for given Software Cluster /// Raw pointer is used here because the memory is deallocated by FlatBuffer. const HMCOREFlatBuffer::HMCOREEcuCfg* flatBuffer_p; +#endif /// Logger object for logging messages logging::PhmLogger& logger_r{logging::PhmLogger::getLogger(logging::PhmLogger::EContext::factory)}; diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory_new.cpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory_new.cpp new file mode 100644 index 000000000..a870df1af --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/MachineConfigFactory_new.cpp @@ -0,0 +1,109 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ +#include "score/mw/launch_manager/alive_monitor/details/factory/MachineConfigFactory.hpp" + +#include +#include +#include "score/mw/launch_manager/alive_monitor/details/timers/TimeConversion.hpp" + + +namespace score +{ +namespace lcm +{ +namespace saf +{ +namespace factory +{ + +using Config = score::mw::launch_manager::configuration::Config; +using DeviceConfigurations = watchdog::IDeviceConfigFactory::DeviceConfigurations; +using NanoSecondType = timers::NanoSecondType; + +namespace +{ +static constexpr char const* kLogPrefix{"Factory for FlatCfg MachineConfig:"}; +} // namespace + +MachineConfigFactory::MachineConfigFactory() noexcept(true) : watchdog::IDeviceConfigFactory() +{ +} + +bool MachineConfigFactory::init(const Config& config) noexcept(false) +{ + const auto& alive_sup = config.aliveSupervision(); + assert(alive_sup.evaluation_cycle_ms != 0U && "evaluation_cycle_ms must not be zero"); + cycleTimeNs = timers::TimeConversion::convertMilliSecToNanoSec(static_cast(alive_sup.evaluation_cycle_ms)); + + const auto& wd_opt = config.watchdog(); + if (wd_opt.has_value()) + { + const auto& wd = *wd_opt; + watchdog::DeviceConfig wdConfig{}; + wdConfig.fileName = wd.device_file_path; + wdConfig.timeoutMax = static_cast(wd.max_timeout_ms); + wdConfig.canBeDeactivated = wd.deactivate_on_shutdown; + wdConfig.needsMagicClose = wd.require_magic_close; + watchdogConfigs.push_back(std::move(wdConfig)); + } + + logger_r.LogInfo() << kLogPrefix << "Loading of HM Machine Configuration succeeded."; + logConfiguration(); + return true; +} + +std::optional MachineConfigFactory::getDeviceConfigurations() const +{ + return watchdogConfigs; +} + +NanoSecondType MachineConfigFactory::getCycleTimeInNs() const noexcept(true) +{ + return cycleTimeNs; +} + +const MachineConfigFactory::SupervisionBufferConfig& MachineConfigFactory::getSupervisionBufferConfig() const + noexcept(true) +{ + return supBufferCfg; +} + +void MachineConfigFactory::logConfiguration() noexcept(true) +{ + logger_r.LogDebug() << kLogPrefix << "Alive Supervision buffer size:" << supBufferCfg.bufferSizeAliveSupervision; + logger_r.LogDebug() << kLogPrefix << "Monitor buffer size:" << supBufferCfg.bufferSizeMonitor; + logger_r.LogDebug() << kLogPrefix << "Periodicity:" << getCycleTimeInNs() << "ns"; + logger_r.LogDebug() << kLogPrefix << "Configured watchdogs:" << watchdogConfigs.size(); + std::uint32_t wdgCount{1U}; + for (const auto& wdgConfig : watchdogConfigs) + { + const std::string_view wdgMagicCloseBool{wdgConfig.needsMagicClose ? "true" : "false"}; + const std::string_view wdgDeactivatedBool{wdgConfig.canBeDeactivated ? "true" : "false"}; + logger_r.LogDebug() << kLogPrefix << "Watchdog" << wdgCount << "- device file:" << wdgConfig.fileName; + logger_r.LogDebug() << kLogPrefix << "Watchdog" << wdgCount << "- max timeout:" << wdgConfig.timeoutMax << "ms"; + logger_r.LogDebug() << kLogPrefix << "Watchdog" << wdgCount << "- needs magic close:" << wdgMagicCloseBool; + logger_r.LogDebug() << kLogPrefix << "Watchdog" << wdgCount + << "- deactivate on hm shutdown:" << wdgDeactivatedBool; + ++wdgCount; + } + + if (watchdogConfigs.empty()) + { + logger_r.LogWarn() << kLogPrefix << "No watchdog configured"; + } +} + +} // namespace factory +} // namespace saf +} // namespace lcm +} // namespace score diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/StaticConfig.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/StaticConfig.hpp index 74e4acfb4..143417fa5 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/StaticConfig.hpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/StaticConfig.hpp @@ -39,6 +39,9 @@ class StaticConfig static constexpr uint16_t k_DefaultAliveSupCheckpointBufferElements{100U}; /// Default buffer size of a Monitor (shared memory) static constexpr uint16_t k_DefaultMonitorBufferElements{ifappl::k_maxCheckpointBufferElements}; + + /// Default checkpoint ID used when creating supervision checkpoints + static constexpr uint32_t k_DefaultCheckpointId{1U}; /// @brief By default hm daemon shutdown is disabled static constexpr bool k_hmDaemonDefaultShutdownEnabled{false}; diff --git a/score/launch_manager/src/daemon/src/common/BUILD b/score/launch_manager/src/daemon/src/common/BUILD index db23911e2..77841e63a 100644 --- a/score/launch_manager/src/daemon/src/common/BUILD +++ b/score/launch_manager/src/daemon/src/common/BUILD @@ -62,6 +62,14 @@ cc_library( ], ) +cc_library( + name = "alive_interface_path", + hdrs = ["alive_interface_path.hpp"], + include_prefix = "score/mw/launch_manager/common", + strip_include_prefix = "/score/launch_manager/src/daemon/src/common", + visibility = ["//score:__subpackages__"], +) + cc_library( name = "process_group_state_id", hdrs = [ diff --git a/score/launch_manager/src/daemon/src/common/alive_interface_path.hpp b/score/launch_manager/src/daemon/src/common/alive_interface_path.hpp new file mode 100644 index 000000000..da9b7b401 --- /dev/null +++ b/score/launch_manager/src/daemon/src/common/alive_interface_path.hpp @@ -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 + ********************************************************************************/ + +#ifndef ALIVE_INTERFACE_PATH_HPP_INCLUDED +#define ALIVE_INTERFACE_PATH_HPP_INCLUDED + +#include + +namespace score +{ +namespace lcm +{ +namespace internal +{ + +/// Returns the IPC socket path for the alive monitoring interface of a component. +inline std::string aliveInterfacePath(const std::string& component_name) +{ + return "/lifecycle_health_" + component_name; +} + +} // namespace internal +} // namespace lcm +} // namespace score + +#endif // ALIVE_INTERFACE_PATH_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/configuration/BUILD b/score/launch_manager/src/daemon/src/configuration/BUILD index 140934e1d..8d6fe7eb3 100644 --- a/score/launch_manager/src/daemon/src/configuration/BUILD +++ b/score/launch_manager/src/daemon/src/configuration/BUILD @@ -136,3 +136,39 @@ cc_library( "@score_baselibs//score/flatbuffers:flatbufferscpp", ], ) + +cc_library( + name = "configuration_adapter", + srcs = ["configuration_adapter.cpp"], + hdrs = ["configuration_adapter.hpp"], + include_prefix = "score/mw/launch_manager/configuration", + strip_include_prefix = "/score/launch_manager/src/daemon/src/configuration", + visibility = ["//score:__subpackages__"], + deps = [ + ":config", + ":config_loader", + ":flatbuffer_config_loader", + "//score/launch_manager/src/daemon/src/common:alive_interface_path", + "//score/launch_manager/src/daemon/src/common:constants", + "//score/launch_manager/src/daemon/src/common:identifier_hash", + "//score/launch_manager/src/daemon/src/common:log", + "//score/launch_manager/src/daemon/src/common:process_group_state_id", + "//score/launch_manager/src/daemon/src/osal:num_cores", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + "//score/launch_manager/src/daemon/src/process_state_client:posix_process", + ], +) + +cc_test( + name = "configuration_adapter_UT", + srcs = ["configuration_adapter_UT.cpp"], + visibility = ["//tests:__subpackages__"], + deps = [ + ":configuration_adapter", + "//score/launch_manager/src/daemon/src/osal:num_cores", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + "//score/launch_manager/src/daemon/src/process_state_client:posix_process", + "@googletest//:gtest_main", + "@score_baselibs//score/flatbuffers:flatbufferscpp", + ], +) diff --git a/score/launch_manager/src/daemon/src/configuration/Readme.md b/score/launch_manager/src/daemon/src/configuration/Readme.md new file mode 100644 index 000000000..64206565b --- /dev/null +++ b/score/launch_manager/src/daemon/src/configuration/Readme.md @@ -0,0 +1,13 @@ +# Note for Developers + +The `ConfigurationAdapter` serves as a temporary bridge that translates the new Config model (RunTargets, Components) into the +legacy API (ProcessGroups, ProcessGroupStates, OsProcess) consumed by ProcessGroupManager, Graph, +and related code. It exists only to decouple the config-format migration from the broader code migration and should be removed once ProcessGroupManager and its dependents are adapted to work directly with RunTarget/Component concepts. + +Ensure that the following tests are successfull at every stage of the new configuration adaption: + +```bash +bazel test --config=x86_64-linux --//config:use_new_configuration=True //score/launch_manager/... +bazel test --config=x86_64-linux --//config:use_new_configuration=True //tests/integration/... +bazel test --config=x86_64-linux --//config:use_new_configuration=True //examples/... +``` diff --git a/score/launch_manager/src/daemon/src/configuration/configuration_adapter.cpp b/score/launch_manager/src/daemon/src/configuration/configuration_adapter.cpp new file mode 100644 index 000000000..bd4de8228 --- /dev/null +++ b/score/launch_manager/src/daemon/src/configuration/configuration_adapter.cpp @@ -0,0 +1,462 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +#include "score/mw/launch_manager/configuration/configuration_adapter.hpp" +#include "score/mw/launch_manager/common/alive_interface_path.hpp" +#include "score/mw/launch_manager/common/log.hpp" +#include "score/mw/launch_manager/osal/num_cores.hpp" + +#include +#include +#include +#include +#include + +namespace score::mw::launch_manager::configuration { + +namespace { +constexpr const char* kAliveInterfaceEnvName = "LCM_ALIVE_INTERFACE_PATH"; +constexpr uint32_t kDefaultProcessExecutionError = 1U; + +uint32_t defaultProcessorAffinityMask() { + return static_cast((1ULL << score::lcm::internal::osal::getNumCores()) - 1ULL); +} +} // namespace + +score::lcm::internal::osal::CommsType ConfigurationAdapter::mapApplicationType( + ApplicationType app_type) const { + switch (app_type) { + case ApplicationType::Reporting: + case ApplicationType::ReportingAndSupervised: + return score::lcm::internal::osal::CommsType::kReporting; + case ApplicationType::StateManager: + return score::lcm::internal::osal::CommsType::kControlClient; + case ApplicationType::Native: + default: + return score::lcm::internal::osal::CommsType::kNoComms; + } +} + +bool ConfigurationAdapter::initialize(const Config& config) { + return buildFromConfig(config); +} + +void ConfigurationAdapter::deinitialize() { + for (auto& process_group : process_groups_) { + for (auto& process : process_group.processes_) { + for (size_t i = 0U; + i < score::lcm::internal::kArgvArraySize && process.startup_config_.argv_[i] != nullptr; + ++i) { + free(const_cast(process.startup_config_.argv_[i])); + process.startup_config_.argv_[i] = nullptr; + } + for (size_t i = 0U; process.startup_config_.envp_[i] != nullptr; ++i) { + free(process.startup_config_.envp_[i]); + process.startup_config_.envp_[i] = nullptr; + } + } + } + component_by_name_.clear(); + component_to_process_index_.clear(); +} + +void ConfigurationAdapter::fillStartupConfigFromDeployment( + const ComponentConfig& comp, + score::lcm::internal::osal::OsalConfig& startup) const { + const auto& deploy = comp.deployment_config; + const auto& props = comp.component_properties; + + startup.executable_path_ = deploy.bin_dir + "/" + props.binary_name; + startup.short_name_ = comp.name; + + startup.uid_ = deploy.sandbox.uid; + startup.gid_ = deploy.sandbox.gid; + startup.supplementary_gids_ = deploy.sandbox.supplementary_group_ids; + startup.security_policy_ = deploy.sandbox.security_policy.value_or(""); + startup.scheduling_policy_ = deploy.sandbox.scheduling_policy; + startup.scheduling_priority_ = deploy.sandbox.scheduling_priority; + startup.cpu_mask_ = defaultProcessorAffinityMask(); + + startup.resource_limits_.stack_ = 0U; + startup.resource_limits_.cpu_ = 0U; + startup.resource_limits_.data_ = 0U; + startup.resource_limits_.as_ = 0U; + if (deploy.sandbox.max_memory_usage.has_value()) { + startup.resource_limits_.as_ = *deploy.sandbox.max_memory_usage; + } + + startup.comms_type_ = mapApplicationType(props.application_profile.application_type); +} + +void ConfigurationAdapter::fillStartupArguments( + const ComponentProperties& props, + score::lcm::internal::osal::OsalConfig& startup) const { + // strdup() returns nullptr on OOM. On this embedded target, OOM during daemon + // startup is unrecoverable — the OS will terminate the process. + size_t arg_index = 0U; + startup.argv_[arg_index++] = strdup(startup.executable_path_.c_str()); + + assert(props.process_arguments.size() <= score::lcm::internal::kMaxArg + && "Too many process arguments for argv array"); + size_t max_args = std::min(props.process_arguments.size(), + static_cast(score::lcm::internal::kMaxArg)); + for (size_t i = 0U; i < max_args; ++i) { + startup.argv_[arg_index++] = strdup(props.process_arguments[i].c_str()); + } +} + +size_t ConfigurationAdapter::fillStartupEnvironment( + const DeploymentConfig& deploy, + score::lcm::internal::osal::OsalConfig& startup) const { + size_t env_index = 0U; + + assert(deploy.environmental_variables.size() + 1U <= score::lcm::internal::kMaxEnv + && "Too many environmental variables for envp array"); + size_t max_env = std::min(deploy.environmental_variables.size(), + static_cast(score::lcm::internal::kMaxEnv)); + size_t env_count = 0; + for (const auto& ev : deploy.environmental_variables) { + if (env_count >= max_env) { + break; + } + + std::string env_str = std::string(ev.key()) + "=" + std::string(ev.value()); + startup.envp_[env_index++] = strdup(env_str.c_str()); + ++env_count; + } + + return env_index; +} + +void ConfigurationAdapter::appendAliveInterfaceEnvironment( + const ComponentConfig& comp, + size_t& env_index, + score::lcm::internal::osal::OsalConfig& startup) const { + bool is_supervised = + comp.component_properties.application_profile.application_type == ApplicationType::ReportingAndSupervised || + comp.component_properties.application_profile.application_type == ApplicationType::StateManager; + if (!is_supervised || env_index >= static_cast(score::lcm::internal::kMaxEnv)) { + return; + } + + std::string iface_path = + std::string(kAliveInterfaceEnvName) + "=" + score::lcm::internal::aliveInterfacePath(comp.name); + startup.envp_[env_index++] = strdup(iface_path.c_str()); +} + +PgManagerConfig ConfigurationAdapter::buildPgManagerConfig(const ComponentConfig& comp) const { + PgManagerConfig pgm{}; + const auto& deploy = comp.deployment_config; + const auto& props = comp.component_properties; + + pgm.is_self_terminating_ = props.application_profile.is_self_terminating; + pgm.startup_timeout_ms_ = std::chrono::milliseconds(deploy.ready_timeout_ms); + pgm.termination_timeout_ms_ = std::chrono::milliseconds(deploy.shutdown_timeout_ms); + pgm.execution_error_code_ = kDefaultProcessExecutionError; + pgm.number_of_restart_attempts = 0U; + if (deploy.ready_recovery_action.has_value()) { + pgm.number_of_restart_attempts = deploy.ready_recovery_action->number_of_attempts; + } + + return pgm; +} + +DependencyList ConfigurationAdapter::buildDependencyList(const ComponentProperties& props) const { + DependencyList dependencies; + dependencies.reserve(props.depends_on.size()); + + for (const auto& dep_name : props.depends_on) { + Dependency dep{}; + dep.process_state_ = score::lcm::ProcessState::kRunning; + + auto dep_it = component_by_name_.find(dep_name); + if (dep_it != component_by_name_.end()) { + const auto& dep_props = dep_it->second->component_properties; + if (dep_props.ready_condition.has_value()) { + dep.process_state_ = dep_props.ready_condition->process_state == ProcessState::Running + ? score::lcm::ProcessState::kRunning + : score::lcm::ProcessState::kTerminated; + } + } + + dep.target_process_id_ = IdentifierHash{dep_name}; + dependencies.push_back(dep); + } + + return dependencies; +} + +OsProcess ConfigurationAdapter::buildOsProcess( + const ComponentConfig& comp, + uint32_t process_index) const { + OsProcess os_process{}; + os_process.process_id_ = IdentifierHash{comp.name}; + os_process.process_number_ = process_index; + + auto& startup = os_process.startup_config_; + fillStartupConfigFromDeployment(comp, startup); + fillStartupArguments(comp.component_properties, startup); + + size_t env_index = fillStartupEnvironment(comp.deployment_config, startup); + appendAliveInterfaceEnvironment(comp, env_index, startup); + startup.envp_[env_index] = nullptr; + + os_process.pgm_config_ = buildPgManagerConfig(comp); + os_process.dependencies_ = buildDependencyList(comp.component_properties); + + return os_process; +} + +void ConfigurationAdapter::resolveDependsOnEntry( + const std::string& dep_name, + const DependsOnMap& depends_on_by_name, + std::vector& indexes, + std::set& visited) const { + if (!visited.insert(dep_name).second) { + return; + } + + bool found = false; + auto comp_it = component_to_process_index_.find(dep_name); + if (comp_it != component_to_process_index_.end()) { + found = true; + if (std::find(indexes.begin(), indexes.end(), comp_it->second) == indexes.end()) { + indexes.push_back(comp_it->second); + } + + auto comp_cfg = component_by_name_.find(dep_name); + if (comp_cfg != component_by_name_.end()) { + for (const auto& sub_dep : comp_cfg->second->component_properties.depends_on) { + resolveDependsOnEntry(sub_dep, depends_on_by_name, indexes, visited); + } + } + } + + auto dep_it = depends_on_by_name.find(dep_name); + if (dep_it != depends_on_by_name.end()) { + found = true; + for (const auto& sub_dep : *dep_it->second) { + resolveDependsOnEntry(sub_dep, depends_on_by_name, indexes, visited); + } + } + + assert(found && "depends_on references unknown component or run_target"); +} + +ProcessGroupState ConfigurationAdapter::buildProcessGroupState( + const std::string& state_name, + const std::vector& depends_on, + const DependsOnMap& depends_on_by_name) const { + ProcessGroupState state; + state.name_ = IdentifierHash{state_name}; + + std::set visited; + for (const auto& dep_name : depends_on) { + resolveDependsOnEntry(dep_name, depends_on_by_name, state.process_indexes_, visited); + } + + return state; +} + +std::vector ConfigurationAdapter::buildProcessGroupStates( + const Config& config) const { + std::vector states; + states.push_back(ProcessGroupState{IdentifierHash{"MainPG/Off"}, {}}); + + const auto& run_targets = config.runTargets(); + DependsOnMap depends_on_by_name; + for (const auto& rt : run_targets) { + depends_on_by_name[rt.name] = &rt.depends_on; + } + + const auto& fallback_cfg = config.fallbackRunTarget(); + depends_on_by_name["fallback_run_target"] = &fallback_cfg.depends_on; + + for (const auto& rt : run_targets) { + states.push_back(buildProcessGroupState("MainPG/" + rt.name, rt.depends_on, depends_on_by_name)); + } + + states.push_back(buildProcessGroupState("MainPG/fallback_run_target", fallback_cfg.depends_on, depends_on_by_name)); + return states; +} + +void ConfigurationAdapter::resolveDependencyIndexes(std::vector& processes) { + for (auto& proc : processes) { + for (auto& dep : proc.dependencies_) { + for (uint32_t idx = 0; idx < processes.size(); ++idx) { + if (processes[idx].process_id_ == dep.target_process_id_) { + dep.os_process_index_ = idx; + break; + } + } + } + } +} + +bool ConfigurationAdapter::buildFromConfig(const Config& config) { + const std::string initial_run_target_name = std::string(config.initialRunTarget()); + + ProcessGroup pg; + const IdentifierHash pg_name{"MainPG"}; + pg.name_ = pg_name; + pg.sw_cluster_ = IdentifierHash{"DefaultSoftwareCluster"}; + pg.off_state_ = IdentifierHash{"MainPG/Off"}; + + pg.recovery_state_ = IdentifierHash{"MainPG/fallback_run_target"}; + + component_by_name_.clear(); + component_to_process_index_.clear(); + for (const auto& comp : config.components()) { + component_by_name_[comp.name] = ∁ + } + + uint32_t process_index = 0; + for (const auto& comp : config.components()) { + component_to_process_index_[comp.name] = process_index; + pg.processes_.push_back(buildOsProcess(comp, process_index)); + ++process_index; + } + + pg.states_ = buildProcessGroupStates(config); + resolveDependencyIndexes(pg.processes_); + + process_groups_.push_back(std::move(pg)); + process_group_names_.push_back(pg_name); + + main_pg_startup_state_ = score::lcm::internal::ProcessGroupStateID{ + pg_name, + IdentifierHash{std::string("MainPG/") + initial_run_target_name} + }; + + LM_LOG_DEBUG() << "ConfigurationAdapter: Built configuration with " + << process_groups_[0].processes_.size() << " processes and " + << process_groups_[0].states_.size() << " states"; + + return true; +} + +std::optional*> ConfigurationAdapter::getListOfProcessGroups() const { + if (!process_group_names_.empty()) { + return &process_group_names_; + } + return std::nullopt; +} + +std::optional ConfigurationAdapter::getSoftwareCluster(const IdentifierHash& process_group_id) const { + auto pg = getProcessGroupByID(process_group_id); + if (pg) { + return pg->sw_cluster_; + } + return std::nullopt; +} + +std::optional ConfigurationAdapter::getNumberOfOsProcesses(const IdentifierHash& pg_name) const { + auto pg = getProcessGroupByID(pg_name); + if (pg) { + return static_cast(pg->processes_.size()); + } + return std::nullopt; +} + +IdentifierHash ConfigurationAdapter::getNameOfOffState(const IdentifierHash& pg_name) const { + auto pg = getProcessGroupByID(pg_name); + if (pg) { + return pg->off_state_; + } + return IdentifierHash{"Off"}; +} + +IdentifierHash ConfigurationAdapter::getNameOfRecoveryState(const IdentifierHash& pg_name) const { + auto pg = getProcessGroupByID(pg_name); + if (pg) { + return pg->recovery_state_; + } + return IdentifierHash{"Recovery"}; +} + +std::optional ConfigurationAdapter::getMainPGStartupState() const { + if (!process_groups_.empty()) { + return &main_pg_startup_state_; + } + + LM_LOG_DEBUG() << "Startup state requested before configuration is initialized"; + return std::nullopt; +} + +std::optional*> ConfigurationAdapter::getProcessIndexesList( + const score::lcm::internal::ProcessGroupStateID& pg_state_id) const { + auto state = getProcessGroupStateByID(pg_state_id); + if (state) { + return &state->process_indexes_; + } + LM_LOG_DEBUG() << "Process group state '" << pg_state_id.pg_state_name_ + << "' not found in group '" << pg_state_id.pg_name_ << "'."; + return std::nullopt; +} + +std::optional ConfigurationAdapter::getOsProcessConfiguration( + const IdentifierHash& pg_name, const uint32_t index) const { + if (auto pg = getProcessGroupByNameAndIndex(pg_name, index)) { + return &(*pg)->processes_[index]; + } + LM_LOG_DEBUG() << "Unable to retrieve process configuration for process group" << pg_name + << "with index" << index; + return std::nullopt; +} + +std::optional ConfigurationAdapter::getOsProcessDependencies( + const IdentifierHash& pg_name, const uint32_t index) const { + if (auto pg = getProcessGroupByNameAndIndex(pg_name, index)) { + return &(*pg)->processes_[index].dependencies_; + } + LM_LOG_DEBUG() << "Unable to retrieve process dependencies for process group" << pg_name + << "with index" << index; + return std::nullopt; +} + +ProcessGroup* ConfigurationAdapter::getProcessGroupByID(const IdentifierHash& pg_name) const { + for (const auto& pg : process_groups_) { + if (pg.name_ == pg_name) { + return const_cast(&pg); + } + } + return nullptr; +} + +ProcessGroupState* ConfigurationAdapter::getProcessGroupStateByID(const score::lcm::internal::ProcessGroupStateID& pg_id) const { + ProcessGroup* pg = getProcessGroupByID(pg_id.pg_name_); + if (pg) { + for (auto& state : pg->states_) { + if (state.name_ == pg_id.pg_state_name_) { + return &state; + } + } + } + return nullptr; +} + +std::optional ConfigurationAdapter::getProcessGroupByNameAndIndex( + const IdentifierHash& pg_name, const uint32_t index) const { + auto pg = getProcessGroupByID(pg_name); + if (pg) { + if (index < pg->processes_.size()) { + return pg; + } + LM_LOG_DEBUG() << "Process index" << index << "is out of bounds in process group" << pg_name; + } else { + LM_LOG_DEBUG() << "Process group not found:" << pg_name; + } + return std::nullopt; +} + +} // namespace score::mw::launch_manager::configuration diff --git a/score/launch_manager/src/daemon/src/configuration/configuration_adapter.hpp b/score/launch_manager/src/daemon/src/configuration/configuration_adapter.hpp new file mode 100644 index 000000000..1fdcc563a --- /dev/null +++ b/score/launch_manager/src/daemon/src/configuration/configuration_adapter.hpp @@ -0,0 +1,155 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +#ifndef CONFIGURATIONADAPTER_HPP_INCLUDED +#define CONFIGURATIONADAPTER_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include "score/mw/launch_manager/configuration/config.hpp" +#include "score/mw/launch_manager/common/identifier_hash.hpp" +#include "score/mw/launch_manager/common/constants.hpp" +#include "score/mw/launch_manager/common/process_group_state_id.hpp" +#include "score/mw/launch_manager/process_group_manager/iprocess.hpp" +#include "score/mw/launch_manager/process_state_client/posix_process.hpp" + +namespace score::mw::launch_manager::configuration { + +using IdentifierHash = score::lcm::IdentifierHash; + +struct PgManagerConfig final { + bool is_self_terminating_; + std::chrono::milliseconds startup_timeout_ms_; + std::chrono::milliseconds termination_timeout_ms_; + uint32_t number_of_restart_attempts; + uint32_t execution_error_code_; +}; + +struct Dependency final { + score::lcm::ProcessState process_state_; + IdentifierHash target_process_id_; + uint32_t os_process_index_; +}; + +using DependencyList = std::vector; + +struct OsProcess final { + IdentifierHash process_id_; + uint32_t process_number_; + score::lcm::internal::osal::OsalConfig startup_config_{}; + PgManagerConfig pgm_config_; + DependencyList dependencies_; +}; + +struct ProcessGroupState final { + IdentifierHash name_; + std::vector process_indexes_; +}; + +struct ProcessGroup final { + IdentifierHash name_; + IdentifierHash sw_cluster_; + IdentifierHash off_state_; + IdentifierHash recovery_state_; + std::vector states_; + std::vector processes_; +}; + +/// @brief Temporary bridge that translates the new Config model (RunTargets, Components) into the +/// legacy API (ProcessGroups, ProcessGroupStates, OsProcess) consumed by ProcessGroupManager, Graph, +/// and related code. It exists only to decouple the config-format migration from the broader code +/// migration and should be removed once ProcessGroupManager and its dependents are adapted to work +/// directly with RunTarget/Component concepts. +class ConfigurationAdapter final { + public: + /// @brief Initialize from a pre-loaded Config object (preferred — avoids a second file read). + bool initialize(const Config& config); + void deinitialize(); + + std::optional*> getListOfProcessGroups() const; + std::optional getSoftwareCluster(const IdentifierHash& process_group_id) const; + std::optional getNumberOfOsProcesses(const IdentifierHash& pg_name) const; + IdentifierHash getNameOfOffState(const IdentifierHash& pg_name) const; + IdentifierHash getNameOfRecoveryState(const IdentifierHash& pg_name) const; + std::optional getMainPGStartupState() const; + std::optional*> getProcessIndexesList( + const score::lcm::internal::ProcessGroupStateID& process_group_state_id) const; + std::optional getOsProcessConfiguration(const IdentifierHash& pg_name_, + const uint32_t index) const; + std::optional getOsProcessDependencies(const IdentifierHash& process_group_name, + const uint32_t index) const; + + private: + using DependsOnMap = std::map*>; + + bool buildFromConfig(const Config& config); + + OsProcess buildOsProcess(const ComponentConfig& comp, + uint32_t process_index) const; + void fillStartupConfigFromDeployment(const ComponentConfig& comp, + score::lcm::internal::osal::OsalConfig& startup) const; + void fillStartupArguments(const ComponentProperties& props, + score::lcm::internal::osal::OsalConfig& startup) const; + size_t fillStartupEnvironment(const DeploymentConfig& deploy, + score::lcm::internal::osal::OsalConfig& startup) const; + void appendAliveInterfaceEnvironment(const ComponentConfig& comp, + size_t& env_index, + score::lcm::internal::osal::OsalConfig& startup) const; + PgManagerConfig buildPgManagerConfig(const ComponentConfig& comp) const; + DependencyList buildDependencyList(const ComponentProperties& props) const; + + std::vector buildProcessGroupStates( + const Config& config) const; + ProcessGroupState buildProcessGroupState(const std::string& state_name, + const std::vector& depends_on, + const DependsOnMap& depends_on_by_name) const; + void resolveDependsOnEntry(const std::string& dep_name, + const DependsOnMap& depends_on_by_name, + std::vector& indexes, + std::set& visited) const; + + static void resolveDependencyIndexes(std::vector& processes); + + score::lcm::internal::osal::CommsType mapApplicationType(ApplicationType app_type) const; + + ProcessGroup* getProcessGroupByID(const IdentifierHash& pg_name) const; + ProcessGroupState* getProcessGroupStateByID(const score::lcm::internal::ProcessGroupStateID& pg_id) const; + std::optional getProcessGroupByNameAndIndex(const IdentifierHash& pg_name, + const uint32_t index) const; + + std::map component_by_name_{}; + std::map component_to_process_index_{}; + std::vector process_groups_{}; + std::vector process_group_names_{}; + score::lcm::internal::ProcessGroupStateID main_pg_startup_state_{static_cast("MainPG"), + static_cast("MainPG/Startup")}; +}; + +} // namespace score::mw::launch_manager::configuration + +// Aliases for backward compatibility with score::lcm::internal consumers +namespace score::lcm::internal { +using ConfigurationAdapter = score::mw::launch_manager::configuration::ConfigurationAdapter; +using OsProcess = score::mw::launch_manager::configuration::OsProcess; +using DependencyList = score::mw::launch_manager::configuration::DependencyList; +using ProcessGroup = score::mw::launch_manager::configuration::ProcessGroup; +using ProcessGroupState = score::mw::launch_manager::configuration::ProcessGroupState; +using PgManagerConfig = score::mw::launch_manager::configuration::PgManagerConfig; +using Dependency = score::mw::launch_manager::configuration::Dependency; +} // namespace score::lcm::internal + +#endif // CONFIGURATIONADAPTER_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/configuration/configuration_adapter_UT.cpp b/score/launch_manager/src/daemon/src/configuration/configuration_adapter_UT.cpp new file mode 100644 index 000000000..e18d2dd0e --- /dev/null +++ b/score/launch_manager/src/daemon/src/configuration/configuration_adapter_UT.cpp @@ -0,0 +1,567 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ +#include "score/mw/launch_manager/configuration/configuration_adapter.hpp" +#include "score/mw/launch_manager/configuration/config.hpp" + +#include +#include + +#include +#include + +namespace score::mw::launch_manager::configuration { +namespace { + +using ::testing::Eq; +using ::testing::IsNull; +using ::testing::Ne; +using ::testing::NotNull; + +using IdentifierHash = score::lcm::IdentifierHash; + +Config makeMinimalConfig() +{ + ComponentConfig comp_a; + comp_a.name = "comp_a"; + comp_a.description = "Component A"; + comp_a.component_properties.application_profile.application_type = ApplicationType::ReportingAndSupervised; + comp_a.component_properties.application_profile.is_self_terminating = false; + comp_a.component_properties.application_profile.alive_supervision = ComponentAliveSupervision{500, 2, 1, 3}; + comp_a.component_properties.ready_condition = ReadyCondition{ProcessState::Running}; + comp_a.deployment_config.ready_timeout_ms = 500; + comp_a.deployment_config.shutdown_timeout_ms = 500; + comp_a.deployment_config.bin_dir = "/opt/apps"; + comp_a.component_properties.binary_name = "comp_a_bin"; + comp_a.deployment_config.working_dir = "/tmp"; + comp_a.deployment_config.sandbox.uid = 1000; + comp_a.deployment_config.sandbox.gid = 1000; + comp_a.deployment_config.sandbox.scheduling_policy = SCHED_OTHER; + comp_a.deployment_config.sandbox.scheduling_priority = 0; + + ComponentConfig comp_b; + comp_b.name = "comp_b"; + comp_b.description = "Component B"; + comp_b.component_properties.application_profile.application_type = ApplicationType::Native; + comp_b.component_properties.application_profile.is_self_terminating = true; + comp_b.component_properties.depends_on = {"comp_a"}; + comp_b.component_properties.ready_condition = ReadyCondition{ProcessState::Running}; + comp_b.deployment_config.ready_timeout_ms = 1000; + comp_b.deployment_config.shutdown_timeout_ms = 1000; + comp_b.deployment_config.bin_dir = "/opt/apps"; + comp_b.component_properties.binary_name = "comp_b_bin"; + comp_b.deployment_config.working_dir = "/tmp"; + comp_b.deployment_config.sandbox.uid = 1000; + comp_b.deployment_config.sandbox.gid = 1000; + comp_b.deployment_config.sandbox.scheduling_policy = SCHED_OTHER; + comp_b.deployment_config.sandbox.scheduling_priority = 0; + + std::vector components; + components.push_back(std::move(comp_a)); + components.push_back(std::move(comp_b)); + + RunTargetConfig startup; + startup.name = "Startup"; + startup.description = "Initial run target"; + startup.depends_on = {"comp_a"}; + startup.transition_timeout_ms = 5000; + startup.recovery_action.run_target = "fallback_run_target"; + + RunTargetConfig full; + full.name = "Full"; + full.description = "Full run target"; + full.depends_on = {"comp_b", "Startup"}; + full.transition_timeout_ms = 5000; + full.recovery_action.run_target = "fallback_run_target"; + + std::vector run_targets; + run_targets.push_back(std::move(startup)); + run_targets.push_back(std::move(full)); + + FallbackRunTargetConfig fallback; + fallback.description = "Safe state"; + fallback.transition_timeout_ms = 1500; + + AliveSupervisionConfig alive; + alive.evaluation_cycle_ms = 500; + + return ConfigBuilder{} + .setComponents(std::move(components)) + .setRunTargets(std::move(run_targets)) + .setInitialRunTarget("Startup") + .setFallbackRunTarget(std::move(fallback)) + .setAliveSupervision(alive) + .build(); +} + +class ConfigurationAdapterTest : public ::testing::Test { + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + + config_ = makeMinimalConfig(); + adapter_.initialize(config_); + } + + void TearDown() override + { + adapter_.deinitialize(); + } + + Config config_{makeMinimalConfig()}; + ConfigurationAdapter adapter_; +}; + +TEST_F(ConfigurationAdapterTest, GetListOfProcessGroupsReturnsSingleImplicitGroup) +{ + RecordProperty("Description", "After initialize, getListOfProcessGroups returns a list with the single MainPG group."); + + auto result = adapter_.getListOfProcessGroups(); + + ASSERT_TRUE(result.has_value()); + const auto& groups = **result; + ASSERT_THAT(groups.size(), Eq(1U)); + EXPECT_THAT(groups[0], Eq(IdentifierHash{"MainPG"})); +} + +TEST_F(ConfigurationAdapterTest, GetMainPGStartupStateMapsToInitialRunTarget) +{ + RecordProperty("Description", "getMainPGStartupState returns MainPG/Startup matching Config::initialRunTarget."); + + auto result = adapter_.getMainPGStartupState(); + + ASSERT_TRUE(result.has_value()); + const auto* state_id = *result; + EXPECT_THAT(state_id->pg_name_, Eq(IdentifierHash{"MainPG"})); + EXPECT_THAT(state_id->pg_state_name_, Eq(IdentifierHash{"MainPG/Startup"})); +} + +TEST_F(ConfigurationAdapterTest, GetNameOfOffStateMapsToMainPGOff) +{ + RecordProperty("Description", "getNameOfOffState returns MainPG/Off for the implicit process group."); + + auto off_state = adapter_.getNameOfOffState(IdentifierHash{"MainPG"}); + + EXPECT_THAT(off_state, Eq(IdentifierHash{"MainPG/Off"})); +} + +TEST_F(ConfigurationAdapterTest, GetNameOfRecoveryStateMapsToFallbackRunTarget) +{ + RecordProperty("Description", "getNameOfRecoveryState returns MainPG/fallback_run_target."); + + auto recovery = adapter_.getNameOfRecoveryState(IdentifierHash{"MainPG"}); + + EXPECT_THAT(recovery, Eq(IdentifierHash{"MainPG/fallback_run_target"})); +} + +TEST_F(ConfigurationAdapterTest, GetProcessIndexesListResolvesRunTargetDependencies) +{ + RecordProperty("Description", "getProcessIndexesList resolves RunTarget depends_on to component indexes."); + + score::lcm::internal::ProcessGroupStateID startup_id{ + IdentifierHash{"MainPG"}, IdentifierHash{"MainPG/Startup"}}; + + auto result = adapter_.getProcessIndexesList(startup_id); + + ASSERT_TRUE(result.has_value()); + const auto& indexes = **result; + EXPECT_THAT(indexes.size(), Eq(1U)); + EXPECT_THAT(indexes[0], Eq(0U)); +} + +TEST_F(ConfigurationAdapterTest, GetProcessIndexesListResolvesTransitiveDependencies) +{ + RecordProperty("Description", "Full run target depends on comp_b and Startup; transitive resolution yields both component indexes."); + + score::lcm::internal::ProcessGroupStateID full_id{ + IdentifierHash{"MainPG"}, IdentifierHash{"MainPG/Full"}}; + + auto result = adapter_.getProcessIndexesList(full_id); + + ASSERT_TRUE(result.has_value()); + const auto& indexes = **result; + EXPECT_THAT(indexes.size(), Eq(2U)); + + bool has_comp_a = std::find(indexes.begin(), indexes.end(), 0U) != indexes.end(); + bool has_comp_b = std::find(indexes.begin(), indexes.end(), 1U) != indexes.end(); + EXPECT_TRUE(has_comp_a); + EXPECT_TRUE(has_comp_b); +} + +TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationMapsComponentFields) +{ + RecordProperty("Description", "getOsProcessConfiguration maps ComponentConfig fields to legacy OsProcess struct."); + + IdentifierHash pg_name{"MainPG"}; + + auto result = adapter_.getOsProcessConfiguration(pg_name, 0U); + + ASSERT_TRUE(result.has_value()); + const auto* os_proc = *result; + + EXPECT_THAT(os_proc->process_id_, Eq(IdentifierHash{"comp_a"})); + EXPECT_THAT(os_proc->process_number_, Eq(0U)); + EXPECT_THAT(os_proc->startup_config_.executable_path_, Eq("/opt/apps/comp_a_bin")); + EXPECT_THAT(os_proc->startup_config_.short_name_, Eq("comp_a")); + EXPECT_THAT(os_proc->startup_config_.uid_, Eq(1000U)); + EXPECT_THAT(os_proc->startup_config_.gid_, Eq(1000U)); + EXPECT_THAT(os_proc->pgm_config_.is_self_terminating_, Eq(false)); + EXPECT_THAT(os_proc->pgm_config_.startup_timeout_ms_, Eq(std::chrono::milliseconds{500})); + EXPECT_THAT(os_proc->pgm_config_.termination_timeout_ms_, Eq(std::chrono::milliseconds{500})); +} + +TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationInjectsAliveInterfaceForSupervised) +{ + RecordProperty("Description", "Supervised components get LCM_ALIVE_INTERFACE_PATH injected into envp."); + + IdentifierHash pg_name{"MainPG"}; + auto result = adapter_.getOsProcessConfiguration(pg_name, 0U); + ASSERT_TRUE(result.has_value()); + const auto* os_proc = *result; + + bool found = false; + for (size_t i = 0; os_proc->startup_config_.envp_[i] != nullptr; ++i) { + if (std::string(os_proc->startup_config_.envp_[i]) == "LCM_ALIVE_INTERFACE_PATH=/lifecycle_health_comp_a") { + found = true; + break; + } + } + EXPECT_TRUE(found) << "LCM_ALIVE_INTERFACE_PATH not found in supervised component envp"; +} + +TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationNoAliveInterfaceForNative) +{ + RecordProperty("Description", "Native components do not get LCM_ALIVE_INTERFACE_PATH injected."); + + IdentifierHash pg_name{"MainPG"}; + auto result = adapter_.getOsProcessConfiguration(pg_name, 1U); + ASSERT_TRUE(result.has_value()); + const auto* os_proc = *result; + + for (size_t i = 0; os_proc->startup_config_.envp_[i] != nullptr; ++i) { + EXPECT_THAT(std::string(os_proc->startup_config_.envp_[i]).find("LCM_ALIVE_INTERFACE_PATH"), + Eq(std::string::npos)) + << "Native component should not have LCM_ALIVE_INTERFACE_PATH"; + } +} + +TEST_F(ConfigurationAdapterTest, GetOsProcessDependenciesMapsComponentDependsOn) +{ + RecordProperty("Description", "getOsProcessDependencies maps component depends_on to legacy Dependency structs."); + + IdentifierHash pg_name{"MainPG"}; + + auto result = adapter_.getOsProcessDependencies(pg_name, 1U); + + ASSERT_TRUE(result.has_value()); + const auto* deps = *result; + ASSERT_THAT(deps->size(), Eq(1U)); + EXPECT_THAT((*deps)[0].target_process_id_, Eq(IdentifierHash{"comp_a"})); + EXPECT_THAT((*deps)[0].os_process_index_, Eq(0U)); + EXPECT_THAT((*deps)[0].process_state_, Eq(score::lcm::ProcessState::kRunning)); +} + +TEST_F(ConfigurationAdapterTest, GetOsProcessDependenciesEmptyForComponentWithNoDeps) +{ + RecordProperty("Description", "Component with no depends_on has an empty dependency list."); + + IdentifierHash pg_name{"MainPG"}; + + auto result = adapter_.getOsProcessDependencies(pg_name, 0U); + + ASSERT_TRUE(result.has_value()); + const auto* deps = *result; + EXPECT_TRUE(deps->empty()); +} + +TEST_F(ConfigurationAdapterTest, GetNumberOfOsProcessesReturnsComponentCount) +{ + RecordProperty("Description", "getNumberOfOsProcesses returns the number of components."); + + auto result = adapter_.getNumberOfOsProcesses(IdentifierHash{"MainPG"}); + + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(2U)); +} + +TEST_F(ConfigurationAdapterTest, GetSoftwareClusterReturnsDefault) +{ + RecordProperty("Description", "getSoftwareCluster returns the default software cluster."); + + auto result = adapter_.getSoftwareCluster(IdentifierHash{"MainPG"}); + + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(IdentifierHash{"DefaultSoftwareCluster"})); +} + +TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationReturnsNulloptForInvalidIndex) +{ + RecordProperty("Description", "Out-of-range index returns nullopt."); + + auto result = adapter_.getOsProcessConfiguration(IdentifierHash{"MainPG"}, 99U); + + EXPECT_FALSE(result.has_value()); +} + +TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationReturnsNulloptForUnknownPG) +{ + RecordProperty("Description", "Unknown process group name returns nullopt."); + + auto result = adapter_.getOsProcessConfiguration(IdentifierHash{"NonExistent"}, 0U); + + EXPECT_FALSE(result.has_value()); +} + +TEST_F(ConfigurationAdapterTest, OffStateReturnsProcessIndexesListEmpty) +{ + RecordProperty("Description", "The Off state has no process indexes."); + + score::lcm::internal::ProcessGroupStateID off_id{ + IdentifierHash{"MainPG"}, IdentifierHash{"MainPG/Off"}}; + + auto result = adapter_.getProcessIndexesList(off_id); + + ASSERT_TRUE(result.has_value()); + EXPECT_TRUE((**result).empty()); +} + +TEST(ConfigurationAdapterReadyConditionTest, DependencyUsesTargetComponentReadyCondition) +{ + RecordProperty("Description", + "The dependency process_state is derived from the target component's ready_condition, not the source's."); + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + + ComponentConfig comp_a; + comp_a.name = "comp_a"; + comp_a.component_properties.application_profile.application_type = ApplicationType::Native; + comp_a.component_properties.application_profile.is_self_terminating = true; + comp_a.component_properties.ready_condition = ReadyCondition{ProcessState::Terminated}; + comp_a.deployment_config.bin_dir = "/opt"; + comp_a.component_properties.binary_name = "comp_a"; + comp_a.deployment_config.working_dir = "/tmp"; + comp_a.deployment_config.sandbox.uid = 0; + comp_a.deployment_config.sandbox.gid = 0; + comp_a.deployment_config.sandbox.scheduling_policy = SCHED_OTHER; + comp_a.deployment_config.sandbox.scheduling_priority = 0; + + ComponentConfig comp_b; + comp_b.name = "comp_b"; + comp_b.component_properties.application_profile.application_type = ApplicationType::Native; + comp_b.component_properties.application_profile.is_self_terminating = false; + comp_b.component_properties.ready_condition = ReadyCondition{ProcessState::Running}; + comp_b.component_properties.depends_on = {"comp_a"}; + comp_b.deployment_config.bin_dir = "/opt"; + comp_b.component_properties.binary_name = "comp_b"; + comp_b.deployment_config.working_dir = "/tmp"; + comp_b.deployment_config.sandbox.uid = 0; + comp_b.deployment_config.sandbox.gid = 0; + comp_b.deployment_config.sandbox.scheduling_policy = SCHED_OTHER; + comp_b.deployment_config.sandbox.scheduling_priority = 0; + + std::vector components; + components.push_back(std::move(comp_a)); + components.push_back(std::move(comp_b)); + + RunTargetConfig startup; + startup.name = "Startup"; + startup.depends_on = {"comp_b"}; + startup.transition_timeout_ms = 5000; + startup.recovery_action.run_target = "fallback_run_target"; + + std::vector run_targets; + run_targets.push_back(std::move(startup)); + + FallbackRunTargetConfig fallback; + fallback.transition_timeout_ms = 1500; + AliveSupervisionConfig alive; + alive.evaluation_cycle_ms = 500; + + auto config = ConfigBuilder{} + .setComponents(std::move(components)) + .setRunTargets(std::move(run_targets)) + .setInitialRunTarget("Startup") + .setFallbackRunTarget(std::move(fallback)) + .setAliveSupervision(alive) + .build(); + + ConfigurationAdapter adapter; + adapter.initialize(config); + + IdentifierHash pg_name{"MainPG"}; + auto result = adapter.getOsProcessDependencies(pg_name, 1U); + ASSERT_TRUE(result.has_value()); + const auto* deps = *result; + ASSERT_THAT(deps->size(), Eq(1U)); + EXPECT_THAT((*deps)[0].target_process_id_, Eq(IdentifierHash{"comp_a"})); + EXPECT_THAT((*deps)[0].process_state_, Eq(score::lcm::ProcessState::kTerminated)) + << "Dependency should use comp_a's ready_condition (Terminated), not comp_b's (Running)"; + + adapter.deinitialize(); +} + +TEST(ConfigurationAdapterReadyConditionTest, DependencyDefaultsToRunningWhenTargetHasNoReadyCondition) +{ + RecordProperty("Description", + "When the dependency target has no ready_condition, the dependency defaults to Running."); + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + + ComponentConfig comp_a; + comp_a.name = "comp_a"; + comp_a.component_properties.application_profile.application_type = ApplicationType::Native; + comp_a.component_properties.application_profile.is_self_terminating = false; + comp_a.deployment_config.bin_dir = "/opt"; + comp_a.component_properties.binary_name = "comp_a"; + comp_a.deployment_config.working_dir = "/tmp"; + comp_a.deployment_config.sandbox.uid = 0; + comp_a.deployment_config.sandbox.gid = 0; + comp_a.deployment_config.sandbox.scheduling_policy = SCHED_OTHER; + comp_a.deployment_config.sandbox.scheduling_priority = 0; + + ComponentConfig comp_b; + comp_b.name = "comp_b"; + comp_b.component_properties.application_profile.application_type = ApplicationType::Native; + comp_b.component_properties.application_profile.is_self_terminating = false; + comp_b.component_properties.ready_condition = ReadyCondition{ProcessState::Terminated}; + comp_b.component_properties.depends_on = {"comp_a"}; + comp_b.deployment_config.bin_dir = "/opt"; + comp_b.component_properties.binary_name = "comp_b"; + comp_b.deployment_config.working_dir = "/tmp"; + comp_b.deployment_config.sandbox.uid = 0; + comp_b.deployment_config.sandbox.gid = 0; + comp_b.deployment_config.sandbox.scheduling_policy = SCHED_OTHER; + comp_b.deployment_config.sandbox.scheduling_priority = 0; + + std::vector components; + components.push_back(std::move(comp_a)); + components.push_back(std::move(comp_b)); + + RunTargetConfig startup; + startup.name = "Startup"; + startup.depends_on = {"comp_b"}; + startup.transition_timeout_ms = 5000; + startup.recovery_action.run_target = "fallback_run_target"; + + std::vector run_targets; + run_targets.push_back(std::move(startup)); + + FallbackRunTargetConfig fallback; + fallback.transition_timeout_ms = 1500; + AliveSupervisionConfig alive; + alive.evaluation_cycle_ms = 500; + + auto config = ConfigBuilder{} + .setComponents(std::move(components)) + .setRunTargets(std::move(run_targets)) + .setInitialRunTarget("Startup") + .setFallbackRunTarget(std::move(fallback)) + .setAliveSupervision(alive) + .build(); + + ConfigurationAdapter adapter; + adapter.initialize(config); + + IdentifierHash pg_name{"MainPG"}; + auto result = adapter.getOsProcessDependencies(pg_name, 1U); + ASSERT_TRUE(result.has_value()); + const auto* deps = *result; + ASSERT_THAT(deps->size(), Eq(1U)); + EXPECT_THAT((*deps)[0].process_state_, Eq(score::lcm::ProcessState::kRunning)) + << "comp_a has no ready_condition, so dependency should default to Running"; + + adapter.deinitialize(); +} + +TEST(ConfigurationAdapterFallbackTest, FallbackRunTargetResolvesDependenciesRecursively) +{ + RecordProperty("Description", + "Fallback run target resolves transitive component dependencies."); + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + + ComponentConfig comp_a; + comp_a.name = "comp_a"; + comp_a.component_properties.application_profile.application_type = ApplicationType::Native; + comp_a.component_properties.application_profile.is_self_terminating = false; + comp_a.deployment_config.bin_dir = "/opt"; + comp_a.component_properties.binary_name = "comp_a"; + comp_a.deployment_config.working_dir = "/tmp"; + comp_a.deployment_config.sandbox.uid = 0; + comp_a.deployment_config.sandbox.gid = 0; + comp_a.deployment_config.sandbox.scheduling_policy = SCHED_OTHER; + comp_a.deployment_config.sandbox.scheduling_priority = 0; + + ComponentConfig comp_b; + comp_b.name = "comp_b"; + comp_b.component_properties.application_profile.application_type = ApplicationType::Native; + comp_b.component_properties.application_profile.is_self_terminating = false; + comp_b.component_properties.depends_on = {"comp_a"}; + comp_b.deployment_config.bin_dir = "/opt"; + comp_b.component_properties.binary_name = "comp_b"; + comp_b.deployment_config.working_dir = "/tmp"; + comp_b.deployment_config.sandbox.uid = 0; + comp_b.deployment_config.sandbox.gid = 0; + comp_b.deployment_config.sandbox.scheduling_policy = SCHED_OTHER; + comp_b.deployment_config.sandbox.scheduling_priority = 0; + + std::vector components; + components.push_back(std::move(comp_a)); + components.push_back(std::move(comp_b)); + + RunTargetConfig startup; + startup.name = "Startup"; + startup.depends_on = {"comp_b"}; + startup.transition_timeout_ms = 5000; + startup.recovery_action.run_target = "fallback_run_target"; + + std::vector run_targets; + run_targets.push_back(std::move(startup)); + + FallbackRunTargetConfig fallback; + fallback.depends_on = {"comp_b"}; + fallback.transition_timeout_ms = 1500; + AliveSupervisionConfig alive; + alive.evaluation_cycle_ms = 500; + + auto config = ConfigBuilder{} + .setComponents(std::move(components)) + .setRunTargets(std::move(run_targets)) + .setInitialRunTarget("Startup") + .setFallbackRunTarget(std::move(fallback)) + .setAliveSupervision(alive) + .build(); + + ConfigurationAdapter adapter; + adapter.initialize(config); + + score::lcm::internal::ProcessGroupStateID fallback_id{ + IdentifierHash{"MainPG"}, IdentifierHash{"MainPG/fallback_run_target"}}; + + auto result = adapter.getProcessIndexesList(fallback_id); + ASSERT_TRUE(result.has_value()); + const auto& indexes = **result; + + EXPECT_THAT(indexes.size(), Eq(2U)) + << "fallback depends on comp_b which depends on comp_a; both should be resolved"; + bool has_comp_a = std::find(indexes.begin(), indexes.end(), 0U) != indexes.end(); + bool has_comp_b = std::find(indexes.begin(), indexes.end(), 1U) != indexes.end(); + EXPECT_TRUE(has_comp_a) << "comp_a (transitive dep of comp_b) should be in fallback indexes"; + EXPECT_TRUE(has_comp_b) << "comp_b (direct dep of fallback) should be in fallback indexes"; + + adapter.deinitialize(); +} + +} // namespace +} // namespace score::mw::launch_manager::configuration diff --git a/score/launch_manager/src/daemon/src/main.cpp b/score/launch_manager/src/daemon/src/main.cpp index 4a9c9038e..0f0da9a67 100644 --- a/score/launch_manager/src/daemon/src/main.cpp +++ b/score/launch_manager/src/daemon/src/main.cpp @@ -23,10 +23,14 @@ #include "score/mw/launch_manager/process_group_manager/process_group_manager.hpp" #include "score/mw/launch_manager/process_state_client/process_state_notifier.hpp" #include "score/mw/launch_manager/recovery_client/recovery_client.hpp" +#ifdef USE_NEW_CONFIGURATION +#include "score/mw/launch_manager/configuration/flatbuffer_config_loader.hpp" +#endif using namespace std; using namespace score::lcm::internal; +#ifndef USE_NEW_CONFIGURATION /// @brief Initializes the LCM daemon. /// This function initializes the LCM daemon by calling the initialize() method /// of the provided ProcessGroupManager object. It logs an information message @@ -46,6 +50,7 @@ bool initializeLCMDaemon(ProcessGroupManager& process_group_manager) return false; } } +#endif /// @brief Runs the LCM daemon. /// This function runs the LCM daemon by calling the run() method of the provided @@ -126,8 +131,33 @@ void reserveFD(int fd) /// @param argv Array of command-line arguments. /// @return The exit code. 0 for success, non-zero for failure. // coverity[autosar_cpp14_a15_3_3_violation:FALSE] Only logging occurs outside the try-catch enclosing main(). +#ifdef USE_NEW_CONFIGURATION +int main(int argc, const char* argv[]) +{ + const char* config_path = "etc/launch_manager_config.bin"; + int opt; + while ((opt = getopt(argc, const_cast(argv), "c:h")) != -1) { + switch (opt) { + case 'c': + config_path = optarg; + break; + case 'h': + std::cout << "Usage: launch_manager [-c ] [-h]\n" + << "\n" + << "Options:\n" + << " -c Path to the flatbuffer config binary.\n" + << " Default: etc/launch_manager_config.bin\n" + << " -h Print this help and exit.\n"; + return EXIT_SUCCESS; + default: + std::cerr << "Usage: launch_manager [-c ] [-h]\n"; + return EXIT_FAILURE; + } + } +#else int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) { +#endif // reserve files descriptor osal::IpcCommsSync::sync_fd (fd3) and // osal::IpcCommsSync::control_client_handler_nudge_fd (fd4) for communication tpyes: kNoComms !fd3 & !fd4 // kReporting fd3 & !fd4 @@ -148,6 +178,14 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) // return EXIT_FAILURE; // } +#ifdef USE_NEW_CONFIGURATION + score::mw::launch_manager::configuration::FlatbufferConfigLoader config_loader; + auto config_result = config_loader.load(config_path); + if (!config_result.has_value()) { + LM_LOG_FATAL() << "Failed to load config from: " << config_path; + return EXIT_FAILURE; + } +#endif LM_LOG_DEBUG() << "Launch Manager Started !!!!"; std::shared_ptr recoveryClient{std::make_shared()}; std::unique_ptr watchdog{ @@ -155,14 +193,22 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) auto process_state_notifier = std::make_unique(); std::unique_ptr healthMonitor{ std::make_unique( - recoveryClient, std::move(watchdog), process_state_notifier->constructReceiver())}; + recoveryClient, std::move(watchdog), process_state_notifier->constructReceiver() +#ifdef USE_NEW_CONFIGURATION + , *config_result +#endif + )}; std::unique_ptr aliveMonitorThread{ std::make_unique(std::move(healthMonitor))}; std::unique_ptr process_group_manager = std::make_unique( std::move(aliveMonitorThread), recoveryClient, std::move(process_state_notifier)); +#ifdef USE_NEW_CONFIGURATION + if (process_group_manager->initialize(*config_result)) +#else if (initializeLCMDaemon(*process_group_manager)) +#endif { if (runLCMDaemon(*process_group_manager)) { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/BUILD index 4faa2b991..8fd583c3c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/BUILD @@ -50,6 +50,10 @@ cc_library( cc_library( name = "process_group_manager_hdrs", hdrs = ["process_group_manager.hpp"], + defines = select({ + "//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"], + "//conditions:default": [], + }), include_prefix = "score/mw/launch_manager/process_group_manager", strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager/details:__pkg__"], @@ -59,7 +63,6 @@ cc_library( "//score/launch_manager/src/daemon/src/common:identifier_hash", "//score/launch_manager/src/daemon/src/common/concurrency:mpmc_concurrent_queue", "//score/launch_manager/src/daemon/src/common/concurrency:workerthread", - "//score/launch_manager/src/daemon/src/configuration:configuration_manager", "//score/launch_manager/src/daemon/src/control:control_client_channel", "//score/launch_manager/src/daemon/src/osal:ipc_comms", "//score/launch_manager/src/daemon/src/process_group_manager/details:graph", @@ -69,7 +72,15 @@ cc_library( "//score/launch_manager/src/daemon/src/process_state_client:iprocess_state_notifier", "//score/launch_manager/src/daemon/src/recovery_client", "@score_baselibs//score/language/futurecpp", - ], + ] + select({ + "//config:lm_use_new_configuration": [ + "//score/launch_manager/src/daemon/src/configuration:config", + "//score/launch_manager/src/daemon/src/configuration:configuration_adapter", + ], + "//conditions:default": [ + "//score/launch_manager/src/daemon/src/configuration:configuration_manager", + ], + }), ) cc_library( diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index f5188d441..2bc35491f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -15,17 +15,28 @@ load("@rules_cc//cc:defs.bzl", "cc_library") cc_library( name = "process_info_node", hdrs = ["process_info_node.hpp"], + defines = select({ + "//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"], + "//conditions:default": [], + }), include_prefix = "score/mw/launch_manager/process_group_manager/details", strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ ":safe_process_map", - "//score/launch_manager/src/daemon/src/configuration:configuration_manager", "//score/launch_manager/src/daemon/src/control:control_client_channel", "//score/launch_manager/src/daemon/src/osal:ipc_comms", "//score/launch_manager/src/daemon/src/osal:semaphore", "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", - ], + "//score/launch_manager/src/daemon/src/process_state_client", + ] + select({ + "//config:lm_use_new_configuration": [ + "//score/launch_manager/src/daemon/src/configuration:configuration_adapter", + ], + "//conditions:default": [ + "//score/launch_manager/src/daemon/src/configuration:configuration_manager", + ], + }), ) cc_library( @@ -38,7 +49,6 @@ cc_library( ":process_info_node", ":safe_process_map", "//score/launch_manager/src/daemon/src/common:identifier_hash", - "//score/launch_manager/src/daemon/src/configuration:configuration_manager", "//score/launch_manager/src/daemon/src/control:control_client_channel", "//score/launch_manager/src/daemon/src/osal:semaphore", "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", @@ -119,6 +129,10 @@ cc_library( "process_group_manager.cpp", "process_info_node.cpp", ], + defines = select({ + "//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"], + "//conditions:default": [], + }), visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ ":graph", @@ -129,12 +143,18 @@ cc_library( "//score/launch_manager/src/daemon/src/common:log", "//score/launch_manager/src/daemon/src/common/concurrency:mpmc_concurrent_queue", "//score/launch_manager/src/daemon/src/common/concurrency:workerthread", - "//score/launch_manager/src/daemon/src/configuration:configuration_manager", "//score/launch_manager/src/daemon/src/osal:ipc_comms", "//score/launch_manager/src/daemon/src/osal:semaphore", "//score/launch_manager/src/daemon/src/process_group_manager:process_group_manager_hdrs", "//score/launch_manager/src/daemon/src/process_state_client", "//score/launch_manager/src/daemon/src/recovery_client", "@score_baselibs//score/language/futurecpp", - ], + ] + select({ + "//config:lm_use_new_configuration": [ + "//score/launch_manager/src/daemon/src/configuration:configuration_adapter", + ], + "//conditions:default": [ + "//score/launch_manager/src/daemon/src/configuration:configuration_manager", + ], + }), ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/graph.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/graph.cpp index b9752d48c..93c32af77 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/graph.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/graph.cpp @@ -18,6 +18,7 @@ #include "score/mw/launch_manager/common/log.hpp" #include "score/mw/launch_manager/process_group_manager/details/graph.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_info_node.hpp" + #include "score/mw/launch_manager/process_group_manager/process_group_manager.hpp" namespace score @@ -63,7 +64,7 @@ Graph::~Graph() void Graph::initProcessGroupNodes(IdentifierHash pg_name, uint32_t num_processes, uint32_t index) { pg_index_ = index; - off_state_ = pgm_->getConfigurationManager()->getNameOfOffState(pg_name); + off_state_ = pgm_->getConfiguration()->getNameOfOffState(pg_name); requested_state_.pg_state_name_ = off_state_; requested_state_.pg_name_ = pg_name; @@ -101,7 +102,7 @@ inline void Graph::createSuccessorLists(IdentifierHash pg_name) // If the other process has a dependency on this one, put it on the correct list auto node_index = node->getNodeIndex(); const DependencyList* dep_list = - pgm_->getConfigurationManager()->getOsProcessDependencies(pg_name, node_index).value_or(nullptr); + pgm_->getConfiguration()->getOsProcessDependencies(pg_name, node_index).value_or(nullptr); if (dep_list) { @@ -279,7 +280,7 @@ bool Graph::startTransition(ProcessGroupStateID pg_state) requested_state_.pg_state_name_ = pg_state.pg_state_name_; } const std::vector* process_index_list = - pgm_->getConfigurationManager()->getProcessIndexesList(requested_state_).value_or(nullptr); + pgm_->getConfiguration()->getProcessIndexesList(requested_state_).value_or(nullptr); if (nullptr != process_index_list) { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/graph.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/graph.hpp index 0e15fcc6f..bbcaf845e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/graph.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/graph.hpp @@ -28,7 +28,6 @@ #include "score/mw/launch_manager/control/control_client_channel.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_info_node.hpp" #include "score/mw/launch_manager/process_group_manager/iprocess.hpp" -#include "score/mw/launch_manager/configuration/configuration_manager.hpp" namespace score { namespace lcm { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager.cpp index 6626da33f..40c94ba54 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager.cpp @@ -40,7 +40,7 @@ void ProcessGroupManager::cancel() ProcessGroupManager::ProcessGroupManager(std::unique_ptr alive_monitor_thread, std::shared_ptr recovery_client, std::unique_ptr process_state_notifier) - : configuration_manager_(), + : configuration_(), process_interface_(), process_map_(nullptr), worker_threads_(nullptr), @@ -69,7 +69,11 @@ void ProcessGroupManager::setLaunchManagerConfiguration(const OsProcess* launch_ } } +#ifdef USE_NEW_CONFIGURATION +bool ProcessGroupManager::initialize(const Config& config) +#else bool ProcessGroupManager::initialize() +#endif { // setup signal handler em_cancelled.store(false); @@ -90,7 +94,11 @@ bool ProcessGroupManager::initialize() sigaction(SIGUSR2, &action, NULL); sigaction(SIGVTALRM, &action, NULL); +#ifdef USE_NEW_CONFIGURATION + if (!initializeControlClientHandler() || !initializeProcessGroups(config)) +#else if (!initializeControlClientHandler() || !initializeProcessGroups()) +#endif { return false; } @@ -117,7 +125,7 @@ void ProcessGroupManager::deinitialize() { // ucm_polling_thread_.stopPolling(); alive_monitor_thread_->stop(); - configuration_manager_.deinitialize(); + configuration_.deinitialize(); process_groups_.clear(); worker_threads_.reset(); @@ -183,13 +191,21 @@ inline bool ProcessGroupManager::initializeControlClientHandler() return result; } +#ifdef USE_NEW_CONFIGURATION +inline bool ProcessGroupManager::initializeProcessGroups(const Config& config) +{ + bool success = false; + + if (configuration_.initialize(config)) +#else inline bool ProcessGroupManager::initializeProcessGroups() { bool success = false; - if (configuration_manager_.initialize()) + if (configuration_.initialize()) +#endif { - auto pg_list = configuration_manager_.getListOfProcessGroups().value_or(nullptr); + auto pg_list = configuration_.getListOfProcessGroups().value_or(nullptr); if (pg_list && !pg_list->empty()) { @@ -200,7 +216,7 @@ inline bool ProcessGroupManager::initializeProcessGroups() for (const auto& pg_name : *pg_list) { - uint32_t num_processes = configuration_manager_.getNumberOfOsProcesses(pg_name).value_or(0); + uint32_t num_processes = configuration_.getNumberOfOsProcesses(pg_name).value_or(0); if (static_cast(total_processes_) + num_processes <= static_cast(score::lcm::internal::ProcessLimits::kMaxProcesses)) @@ -253,13 +269,13 @@ inline void ProcessGroupManager::createProcessComponentsObjects() inline void ProcessGroupManager::initializeGraphNodes() { - auto pg_list = configuration_manager_.getListOfProcessGroups().value_or(nullptr); + auto pg_list = configuration_.getListOfProcessGroups().value_or(nullptr); for (size_t idx = 0U; idx < process_groups_.size(); ++idx) { process_groups_[idx]->initProcessGroupNodes( pg_list->at(idx), - configuration_manager_.getNumberOfOsProcesses(pg_list->at(idx)).value_or(0U), + configuration_.getNumberOfOsProcesses(pg_list->at(idx)).value_or(0U), static_cast(idx & 0xFFFFFFFFUL)); } @@ -309,7 +325,7 @@ inline bool ProcessGroupManager::startInitialTransition() LM_LOG_DEBUG() << "=============STARTING MAINPG STARTUP STATE============"; // Initial transition of machine process group - const ProcessGroupStateID* pg_startup_id = configuration_manager_.getMainPGStartupState().value_or(nullptr); + const ProcessGroupStateID* pg_startup_id = configuration_.getMainPGStartupState().value_or(nullptr); if (pg_startup_id) { @@ -551,7 +567,7 @@ inline void ProcessGroupManager::recoveryActionHandler() } const IdentifierHash old_state = pg->getProcessGroupState(); - const IdentifierHash recovery_state = configuration_manager_.getNameOfRecoveryState(pg->getProcessGroupName()); + const IdentifierHash recovery_state = configuration_.getNameOfRecoveryState(pg->getProcessGroupName()); const GraphState graph_state = pg->getState(); LM_LOG_DEBUG() << "recoveryActionHandler: Processing recovery request for PG " << *recovery_request @@ -681,7 +697,7 @@ inline void ProcessGroupManager::processGetInitialMachineStateTransitionResult(C inline void ProcessGroupManager::processValidateFunctionStateID(ControlClientChannelP scc) { - if (configuration_manager_.getProcessIndexesList(scc->request().process_group_state_)) + if (configuration_.getProcessIndexesList(scc->request().process_group_state_)) { scc->request().request_or_response_ = ControlClientCode::kValidateProcessGroupStateSuccess; } @@ -730,7 +746,7 @@ inline void ProcessGroupManager::processGroupHandler(Graph& pg) ProcessGroupStateID recovery_state; recovery_state.pg_name_ = pg.getProcessGroupName(); - recovery_state.pg_state_name_ = configuration_manager_.getNameOfRecoveryState(recovery_state.pg_name_); + recovery_state.pg_state_name_ = configuration_.getNameOfRecoveryState(recovery_state.pg_name_); LM_LOG_WARN() << "Problem discovered in PG" << recovery_state.pg_name_ << "Activating Recovery state."; @@ -766,10 +782,10 @@ std::shared_ptr ProcessGroupManager::getProcessGroupByProcessId(const Ide for (auto& pg : process_groups_) { const IdentifierHash pg_name = pg->getProcessGroupName(); - const uint32_t count = configuration_manager_.getNumberOfOsProcesses(pg_name).value_or(0U); + const uint32_t count = configuration_.getNumberOfOsProcesses(pg_name).value_or(0U); for (uint32_t idx = 0U; idx < count; ++idx) { - const auto* proc = configuration_manager_.getOsProcessConfiguration(pg_name, idx).value_or(nullptr); + const auto* proc = configuration_.getOsProcessConfiguration(pg_name, idx).value_or(nullptr); if (proc != nullptr && proc->process_id_ == process_id) { return pg; @@ -804,9 +820,9 @@ osal::IProcess* ProcessGroupManager::getProcessInterface() return &process_interface_; } -ConfigurationManager* ProcessGroupManager::getConfigurationManager() +ConfigurationType* ProcessGroupManager::getConfiguration() { - return &configuration_manager_; + return &configuration_; } std::shared_ptr ProcessGroupManager::getProcessMap() diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp index 4134e789d..ca51d3faa 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp @@ -11,6 +11,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +#include + #include "process_info_node.hpp" #include "graph.hpp" #include "score/mw/launch_manager/common/log.hpp" @@ -41,7 +43,7 @@ void ProcessInfoNode::initNode(Graph* graph, uint32_t index) dependent_on_running_.clear(); dependent_on_terminating_.clear(); start_dependencies_ = 0U; - auto cfg_mgr = graph_->getProcessGroupManager()->getConfigurationManager(); + auto cfg_mgr = graph_->getProcessGroupManager()->getConfiguration(); config_ = cfg_mgr->getOsProcessConfiguration(pg, index).value_or(nullptr); if (config_) { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.hpp index a04e6417b..845906932 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.hpp @@ -15,7 +15,12 @@ #define _INCLUDED_PROCESSINFONODE_ #include +#include "score/mw/launch_manager/process_state_client/posix_process.hpp" +#ifdef USE_NEW_CONFIGURATION +#include "score/mw/launch_manager/configuration/configuration_adapter.hpp" +#else #include "score/mw/launch_manager/configuration/configuration_manager.hpp" +#endif #include "score/mw/launch_manager/process_group_manager/details/safe_process_map.hpp" #include "score/mw/launch_manager/control/control_client_channel.hpp" diff --git a/score/launch_manager/src/daemon/src/process_group_manager/process_group_manager.hpp b/score/launch_manager/src/daemon/src/process_group_manager/process_group_manager.hpp index ca5b7a49a..9b2c57c68 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/process_group_manager.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/process_group_manager.hpp @@ -20,7 +20,12 @@ #include "score/mw/launch_manager/common/identifier_hash.hpp" #include "score/mw/launch_manager/control/control_client_channel.hpp" +#ifdef USE_NEW_CONFIGURATION +#include "score/mw/launch_manager/configuration/config.hpp" +#include "score/mw/launch_manager/configuration/configuration_adapter.hpp" +#else #include "score/mw/launch_manager/configuration/configuration_manager.hpp" +#endif #include "score/mw/launch_manager/process_group_manager/iprocess.hpp" #include "score/mw/launch_manager/process_group_manager/details/graph.hpp" #include "score/mw/launch_manager/common/concurrency/mpmc_concurrent_queue.hpp" @@ -36,6 +41,13 @@ namespace score::lcm::internal { +#ifdef USE_NEW_CONFIGURATION +using ConfigurationType = ConfigurationAdapter; +using Config = score::mw::launch_manager::configuration::Config; +#else +using ConfigurationType = ConfigurationManager; +#endif + /// @brief ProcessGroupManager provides the core functionality of LCM. /// Software that is deployed to the machine, should be managed through Process Groups. /// A Process Group (PG) can be described as a set of applications, or executable files, that should be controlled in a @@ -76,7 +88,11 @@ class ProcessGroupManager final /// Creates and initialises the shared memory for the nudge semaphore, always using FD #4, /// and stores a pointer to it. /// @return Returns true if initialization was successful, false otherwise. +#ifdef USE_NEW_CONFIGURATION + bool initialize(const Config& config); +#else bool initialize(); +#endif /// @brief De-initialises the process group manager /// deletes worker threads, worker jobs and the process map and then de-initialises the configuration manager @@ -121,9 +137,9 @@ class ProcessGroupManager final /// @return Pointer to the OSAL process interface. osal::IProcess* getProcessInterface(); - /// @brief Gets the configuration manager. - /// @return Pointer to the ConfigurationManager object. - ConfigurationManager* getConfigurationManager(); + /// @brief Get the configuration object + /// @return a pointer to the configuration object + ConfigurationType* getConfiguration(); /// @brief Gets the process map. /// @return Shared pointer to the SafeProcessMap object. @@ -260,7 +276,12 @@ class ProcessGroupManager final /// @brief Initializes the process groups. /// @return Returns true if initialization was successful, false otherwise. +#ifdef USE_NEW_CONFIGURATION + inline bool initializeProcessGroups(const Config& config); +#else inline bool initializeProcessGroups(); +#endif + /// @brief Creates process component objects, including the job queue and worker threads. inline void createProcessComponentsObjects(); @@ -271,8 +292,8 @@ class ProcessGroupManager final /// @brief Initializes the Control Client handler. inline bool initializeControlClientHandler(); - /// @brief The ConfigurationManager object associated with the ProcessGroupManager. - ConfigurationManager configuration_manager_; + /// @brief The configuration object associated with the ProcessGroupManager. + ConfigurationType configuration_; /// @brief The process interface object associated with the ProcessGroupManager. osal::IProcess process_interface_; diff --git a/scripts/config_mapping/config.bzl b/scripts/config_mapping/config.bzl index aa43ab20f..60fe0fd32 100644 --- a/scripts/config_mapping/config.bzl +++ b/scripts/config_mapping/config.bzl @@ -32,7 +32,7 @@ lm_config_splitter = rule( "config": attr.label( allow_single_file = [".json"], mandatory = True, - doc = "Json file to convert. Note that the binary file will have the same name as the json (minus the suffix)", + doc = "Json file to convert.", ), "schema": attr.label( default = Label("//score/launch_manager/src/daemon/src/configuration/config_schema:launch_manager.schema.json"), @@ -53,14 +53,66 @@ lm_config_splitter = rule( }, ) +def _lm_new_config_gen_impl(ctx): + """Run lifecycle_config.py --new-format to produce a single unified config.""" + script = ctx.executable.script + config = ctx.file.config + schema = ctx.file.schema + base = config.basename + dot_index = base.rfind(".") + stem = base if dot_index == -1 else base[:dot_index] + output_name = "{}_gen.json".format(stem) + out_file = ctx.actions.declare_file("{}/{}".format(ctx.label.name, output_name)) + + ctx.actions.run( + inputs = [config, schema], + outputs = [out_file], + tools = [script], + mnemonic = "LifecycleNewFormatJsonGeneration", + executable = script, + progress_message = "generating new-format Launch Manager config from {}".format(config.short_path), + arguments = [ + config.path, + "--schema", + schema.path, + "-o", + out_file.dirname, + "--new-format", + ], + ) + + return [DefaultInfo(files = depset([out_file]))] + +lm_new_config_generator = rule( + implementation = _lm_new_config_gen_impl, + doc = "Generates a single unified configuration file in the new flatbuffer format.", + attrs = { + "config": attr.label( + allow_single_file = [".json"], + mandatory = True, + doc = "Json file to convert.", + ), + "schema": attr.label( + default = Label("//score/launch_manager/src/daemon/src/configuration/config_schema:launch_manager.schema.json"), + allow_single_file = [".json"], + doc = "Json schema file to validate the input json against", + ), + "script": attr.label( + default = Label("//scripts/config_mapping:lifecycle_config"), + executable = True, + cfg = "exec", + doc = "Python script to execute", + ), + }, +) + def _lm_config_combiner_impl(ctx): - """Declare a single directory that all three serialized buffers get copied - into. Consumers receive this one directory instead of three individual - files. + """Declare a single directory that all serialized buffers get copied into. + Consumers receive this one directory instead of individual files. """ etc_dir = ctx.actions.declare_directory(ctx.attr.dir_name) - srcs = [ctx.file.lm_config, ctx.file.hm_config, ctx.file.hm_core_config] + srcs = ctx.files.srcs args = ctx.actions.args() args.add(etc_dir.path) @@ -81,17 +133,10 @@ lm_config_combiner = rule( implementation = _lm_config_combiner_impl, doc = "Combines the generated .bin files into a single etc directory.", attrs = { - "lm_config": attr.label( - allow_single_file = [".bin"], - mandatory = True, - ), - "hm_config": attr.label( - allow_single_file = [".bin"], - mandatory = True, - ), - "hm_core_config": attr.label( - allow_single_file = [".bin"], + "srcs": attr.label_list( + allow_files = True, mandatory = True, + doc = "List of .bin files to copy into the output directory.", ), "dir_name": attr.string( default = "etc", @@ -107,11 +152,13 @@ def launch_manager_config( script = Label("//scripts/config_mapping:lifecycle_config"), flatbuffer_out_dir = "flatbuffer_out", lm_schema = Label("//score/launch_manager/src/daemon/src/configuration/config_schema:lm_flatcfg.fbs"), + new_lm_schema = Label("//score/launch_manager/src/daemon/src/configuration:new_lm_flatcfg_fbs"), hm_schema = Label("//score/launch_manager/src/daemon/src/alive_monitor/config:hm_flatcfg.fbs"), hmcore_schema = Label("//score/launch_manager/src/daemon/src/alive_monitor/config:hmcore_flatcfg.fbs"), **kwargs): split_name = name + "_split" + # Old format path (3 separate flatbuffer files) # note that the splitting and combining is a workaround as we have to return # the etc directory where all the .bin files are for backwards compatibility. lm_config_splitter( @@ -123,13 +170,13 @@ def launch_manager_config( json_prefix = ":" + split_name + "/" - buffers = [ + old_format_buffers = [ ("_lm_bin", "lm_demo", lm_schema), ("_hm_bin", "hm_demo", hm_schema), ("_hmcore_bin", "hmcore", hmcore_schema), ] - for suffix, stem, schema_file in buffers: + for suffix, stem, schema_file in old_format_buffers: serialize_buffer( name = name + suffix, data = json_prefix + stem + ".json", @@ -138,11 +185,39 @@ def launch_manager_config( **kwargs ) + # New format path (single unified flatbuffer file) + new_gen_name = name + "_new_gen" + lm_new_config_generator( + name = new_gen_name, + config = config, + schema = schema, + script = script, + ) + + config_basename = Label(config).name + dot_index = config_basename.rfind(".") + config_stem = config_basename[:dot_index] if dot_index != -1 else config_basename + + serialize_buffer( + name = name + "_new_lm_bin", + data = ":" + new_gen_name, + schema = new_lm_schema, + output = name + "_serialized_new/" + config_stem + ".bin", + **kwargs + ) + + # Single combiner whose sources are selected by --//config:use_new_configuration + # Always outputs to `flatbuffer_out_dir` (e.g. "etc") so the launch_manager + # binary can find its config at "etc/.bin" regardless of format. lm_config_combiner( name = name, - lm_config = ":" + name + "_lm_bin", - hm_config = ":" + name + "_hm_bin", - hm_core_config = ":" + name + "_hmcore_bin", + srcs = select({ + Label("//config:lm_use_new_configuration"): [":" + name + "_new_lm_bin"], + "//conditions:default": [ + ":" + name + "_lm_bin", + ":" + name + "_hm_bin", + ":" + name + "_hmcore_bin", + ], + }), dir_name = flatbuffer_out_dir, - **kwargs ) diff --git a/scripts/config_mapping/lifecycle_config.py b/scripts/config_mapping/lifecycle_config.py index 5a1b9e486..2346dbcb8 100644 --- a/scripts/config_mapping/lifecycle_config.py +++ b/scripts/config_mapping/lifecycle_config.py @@ -3,7 +3,9 @@ import argparse from copy import deepcopy import json +import os import sys +from pathlib import Path from typing import Dict, Any score_defaults = json.loads(""" @@ -62,6 +64,7 @@ } } }, + "initial_run_target": "Startup", "alive_supervision": { "evaluation_cycle": 0.5 }, @@ -170,9 +173,21 @@ def dict_merge_recursive(dict_a, dict_b): merged_defaults["watchdog"], config.get("watchdog", {}) ) - for key in ("initial_run_target", "fallback_run_target"): - if key in config: - new_config[key] = config[key] + new_config["initial_run_target"] = config.get( + "initial_run_target", merged_defaults["initial_run_target"] + ) + + if "fallback_run_target" in config: + fallback_defaults = { + "description": "", + "depends_on": [], + "transition_timeout": merged_defaults["run_target"].get( + "transition_timeout", 3 + ), + } + new_config["fallback_run_target"] = dict_merge( + fallback_defaults, config["fallback_run_target"] + ) # print(json.dumps(new_config, indent=4)) @@ -186,6 +201,168 @@ def is_supervised(application_type): ) +SCHED_POLICY_MAP = { + "SCHED_OTHER": "OTHER", + "SCHED_FIFO": "FIFO", + "SCHED_RR": "RR", +} + + +def output_filename(input_filename): + stem = Path(input_filename).stem + return f"{stem}_gen.json" + + +def gen_config(output_dir, config, input_filename, schema_version=None): + out = {} + if schema_version is not None: + out["schema_version"] = schema_version + + out["components"] = [] + for component_name, component_config in config["components"].items(): + comp_props = component_config["component_properties"] + depl_cfg = component_config["deployment_config"] + sandbox = depl_cfg["sandbox"] + + component = { + "name": component_name, + "description": component_config.get("description", ""), + } + + app_profile = { + "application_type": comp_props["application_profile"]["application_type"] + } + app_profile["is_self_terminating"] = comp_props["application_profile"].get( + "is_self_terminating", False + ) + if is_supervised(comp_props["application_profile"]["application_type"]): + alive_sup = comp_props["application_profile"].get("alive_supervision", {}) + app_profile["alive_supervision"] = { + "reporting_cycle": alive_sup["reporting_cycle"], + "failed_cycles_tolerance": alive_sup["failed_cycles_tolerance"], + "min_indications": alive_sup["min_indications"], + "max_indications": alive_sup["max_indications"], + } + + props = { + "binary_name": comp_props.get("binary_name", ""), + "application_profile": app_profile, + "depends_on": comp_props.get("depends_on", []), + "process_arguments": comp_props.get("process_arguments", []), + "ready_condition": comp_props.get( + "ready_condition", {"process_state": "Running"} + ), + } + component["component_properties"] = props + + sched_policy = SCHED_POLICY_MAP.get( + sandbox.get("scheduling_policy", "SCHED_OTHER"), + sandbox.get("scheduling_policy", "OTHER"), + ) + + sandbox_out = { + "uid": sandbox["uid"], + "gid": sandbox["gid"], + "supplementary_group_ids": sandbox.get("supplementary_group_ids", []), + "security_policy": sandbox.get("security_policy", ""), + "scheduling_policy": sched_policy, + "scheduling_priority": sandbox.get("scheduling_priority", 0), + } + if "max_memory_usage" in sandbox: + sandbox_out["max_memory_usage"] = sandbox["max_memory_usage"] + if "max_cpu_usage" in sandbox: + sandbox_out["max_cpu_usage"] = sandbox["max_cpu_usage"] + + deployment = { + "ready_timeout": depl_cfg["ready_timeout"], + "shutdown_timeout": depl_cfg["shutdown_timeout"], + "bin_dir": depl_cfg["bin_dir"], + "working_dir": depl_cfg["working_dir"], + "sandbox": sandbox_out, + } + + env_vars = depl_cfg.get("environmental_variables", {}) + if env_vars: + deployment["environmental_variables"] = [ + {"key": k, "value": v} for k, v in env_vars.items() + ] + + if "ready_recovery_action" in depl_cfg: + rra = depl_cfg["ready_recovery_action"] + restart = rra.get("restart", rra) + deployment["ready_recovery_action"] = { + "number_of_attempts": restart.get("number_of_attempts", 0), + "delay_before_restart": restart.get("delay_before_restart", 0), + } + + if "recovery_action" in depl_cfg: + ra = depl_cfg["recovery_action"] + if "switch_run_target" in ra: + deployment["recovery_action"] = { + "run_target": ra["switch_run_target"]["run_target"] + } + else: + deployment["recovery_action"] = ra + + component["deployment_config"] = deployment + out["components"].append(component) + + out["run_targets"] = [] + for rt_name, rt_config in config["run_targets"].items(): + rt = { + "name": rt_name, + "transition_timeout": rt_config.get("transition_timeout", 3), + "recovery_action": { + "run_target": rt_config.get("recovery_action", {}) + .get("switch_run_target", {}) + .get("run_target", "fallback_run_target") + }, + } + if rt_config.get("description"): + rt["description"] = rt_config["description"] + if "depends_on" in rt_config and rt_config["depends_on"]: + rt["depends_on"] = rt_config["depends_on"] + out["run_targets"].append(rt) + + out["initial_run_target"] = config["initial_run_target"] + + fallback = config.get("fallback_run_target", {}) + fb_out = {} + if "transition_timeout" in fallback: + fb_out["transition_timeout"] = fallback["transition_timeout"] + if fallback.get("description"): + fb_out["description"] = fallback["description"] + if "depends_on" in fallback and fallback["depends_on"]: + fb_out["depends_on"] = fallback["depends_on"] + out["fallback_run_target"] = fb_out + + out["alive_supervision"] = { + "evaluation_cycle": config.get("alive_supervision", {}).get( + "evaluation_cycle", 0.5 + ), + } + + watchdog_config = config.get("watchdog", {}) + required_watchdog_fields = { + "device_file_path", + "max_timeout", + "deactivate_on_shutdown", + "require_magic_close", + } + if watchdog_config and required_watchdog_fields.issubset(watchdog_config.keys()): + out["watchdog"] = { + "device_file_path": watchdog_config["device_file_path"], + "max_timeout": watchdog_config["max_timeout"], + "deactivate_on_shutdown": watchdog_config["deactivate_on_shutdown"], + "require_magic_close": watchdog_config["require_magic_close"], + } + + out_filename = output_filename(input_filename) + with open(os.path.join(output_dir, out_filename), "w") as f: + json.dump(out, f, indent=4) + f.write("\n") + + def gen_health_monitor_config(output_dir, config): """ This function generates the health monitor configuration file based on the input configuration. @@ -295,15 +472,22 @@ def get_process_type(application_type): ] if watchdog_config := config.get("watchdog", {}): - watchdog = {} - watchdog["shortName"] = "watchdog" - watchdog["deviceFilePath"] = watchdog_config["device_file_path"] - watchdog["maxTimeout"] = sec_to_ms(watchdog_config["max_timeout"]) - watchdog["deactivateOnShutdown"] = watchdog_config["deactivate_on_shutdown"] - watchdog["hasValueDeactivateOnShutdown"] = True - watchdog["requireMagicClose"] = watchdog_config["require_magic_close"] - watchdog["hasValueRequireMagicClose"] = True - hmcore_config["watchdogs"].append(watchdog) + required_watchdog_fields = { + "device_file_path", + "max_timeout", + "deactivate_on_shutdown", + "require_magic_close", + } + if required_watchdog_fields.issubset(watchdog_config.keys()): + watchdog = {} + watchdog["shortName"] = "watchdog" + watchdog["deviceFilePath"] = watchdog_config["device_file_path"] + watchdog["maxTimeout"] = sec_to_ms(watchdog_config["max_timeout"]) + watchdog["deactivateOnShutdown"] = watchdog_config["deactivate_on_shutdown"] + watchdog["hasValueDeactivateOnShutdown"] = True + watchdog["requireMagicClose"] = watchdog_config["require_magic_close"] + watchdog["hasValueRequireMagicClose"] = True + hmcore_config["watchdogs"].append(watchdog) with open(f"{output_dir}/hmcore.json", "w") as hm_file: json.dump(hmcore_config, hm_file, indent=4) @@ -648,6 +832,12 @@ def main(): help="Only validate the provided configuration file against the schema and exit.", ) parser.add_argument("--schema", help="Path to the JSON schema file for validation") + parser.add_argument( + "--new-format", + default=False, + action="store_true", + help="Generate a single unified configuration file in the new format.", + ) args = parser.parse_args() input_config = load_json_file(args.filename) @@ -686,8 +876,16 @@ def main(): exit(CUSTOM_VALIDATION_FAILURE) try: - gen_health_monitor_config(args.output_dir, preprocessed_config) - gen_launch_manager_config(args.output_dir, preprocessed_config) + if args.new_format: + gen_config( + args.output_dir, + preprocessed_config, + args.filename, + schema_version=input_config.get("schema_version"), + ) + else: + gen_health_monitor_config(args.output_dir, preprocessed_config) + gen_launch_manager_config(args.output_dir, preprocessed_config) except ValueError as e: print(f"Error during configuration generation: {e}", file=sys.stderr) exit(CUSTOM_VALIDATION_FAILURE) diff --git a/tests/integration/complex_monitoring/complex_monitoring.py b/tests/integration/complex_monitoring/complex_monitoring.py index ec47e5952..76ebeee2f 100644 --- a/tests/integration/complex_monitoring/complex_monitoring.py +++ b/tests/integration/complex_monitoring/complex_monitoring.py @@ -32,11 +32,17 @@ def test_complex_monitoring(target, setup_test, assert_test_results, remote_test Expected Behaviour: Health monitor detects the missed heartbeats and triggers a recovery action, activating the fallback run target. """ + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/complex_monitoring.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=4.0, ) diff --git a/tests/integration/crash_on_startup/crash_on_startup.py b/tests/integration/crash_on_startup/crash_on_startup.py index 443d440dc..3380d71a0 100644 --- a/tests/integration/crash_on_startup/crash_on_startup.py +++ b/tests/integration/crash_on_startup/crash_on_startup.py @@ -36,11 +36,17 @@ def test_crash_on_startup(target, setup_test, assert_test_results, remote_test_d Expected Behaviour: Process startup fails, LaunchManager executes recovery action. """ + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/crash_on_startup.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=10.0, ) diff --git a/tests/integration/incorrect_config_non_reporting/test_incorrect_config_non_reporting.py b/tests/integration/incorrect_config_non_reporting/test_incorrect_config_non_reporting.py index 342987054..23bfded13 100644 --- a/tests/integration/incorrect_config_non_reporting/test_incorrect_config_non_reporting.py +++ b/tests/integration/incorrect_config_non_reporting/test_incorrect_config_non_reporting.py @@ -30,11 +30,18 @@ def test_incorrect_config_non_reporting( Input: Component wrongly configured as `native` application type, acquires a file descriptor ordinarily used by LM communication, and reports the Running state to LaunchManager. Expected Outcome: Process does not crash. """ + + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/non_reporting_config.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=2.0, ) diff --git a/tests/integration/process_complex_rep_failure/process_complex_rep_failure.py b/tests/integration/process_complex_rep_failure/process_complex_rep_failure.py index 068861b57..636ce9819 100644 --- a/tests/integration/process_complex_rep_failure/process_complex_rep_failure.py +++ b/tests/integration/process_complex_rep_failure/process_complex_rep_failure.py @@ -41,11 +41,17 @@ def test_recovery_action_complex_rep_failure( The recovery action switches to the fallback run target, the activation of the fallback run target is verified in the test. """ + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/process_complex_rep_failure.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=10.0, ) diff --git a/tests/integration/process_crash_monitoring/process_crash_monitoring.py b/tests/integration/process_crash_monitoring/process_crash_monitoring.py index d81344f86..cf006d713 100644 --- a/tests/integration/process_crash_monitoring/process_crash_monitoring.py +++ b/tests/integration/process_crash_monitoring/process_crash_monitoring.py @@ -31,11 +31,17 @@ def test_process_crash_monitoring( Expected Behaviour: Launch manager detects the crash and activates the fallback run target. """ + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/process_crash_monitoring.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=10.0, ) diff --git a/tests/integration/process_fd_leak/process_fd_leak.py b/tests/integration/process_fd_leak/process_fd_leak.py index 2f5cabdda..f4aca5373 100644 --- a/tests/integration/process_fd_leak/process_fd_leak.py +++ b/tests/integration/process_fd_leak/process_fd_leak.py @@ -25,11 +25,17 @@ def test_process_fd_leak(target, setup_test, assert_test_results, remote_test_di """Tests the inherited file descriptors from LCM for Native, Reporting and State_Manager application types.""" + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/process_fd_leak.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=2.0, ) diff --git a/tests/integration/process_launch_args/process_launch_args.py b/tests/integration/process_launch_args/process_launch_args.py index f10569c01..e52664292 100644 --- a/tests/integration/process_launch_args/process_launch_args.py +++ b/tests/integration/process_launch_args/process_launch_args.py @@ -34,11 +34,17 @@ def test_process_launch_args(target, setup_test, assert_test_results, remote_tes Expected Behaviour: Process starts successfully, reports running, and receives the configured argument value. """ + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/process_launch_args.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=2.0, ) diff --git a/tests/integration/process_simple_rep_failure/process_simple_rep_failure.py b/tests/integration/process_simple_rep_failure/process_simple_rep_failure.py index d890228ab..213def82e 100644 --- a/tests/integration/process_simple_rep_failure/process_simple_rep_failure.py +++ b/tests/integration/process_simple_rep_failure/process_simple_rep_failure.py @@ -41,11 +41,17 @@ def test_recovery_action_simple_rep_failure( The recovery action switches to the fallback run target, the activation of the fallback run target is verified in the test. """ + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/process_simple_rep_failure.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=10.0, ) diff --git a/tests/integration/process_wrong_binary_failure/process_wrong_binary_failure.py b/tests/integration/process_wrong_binary_failure/process_wrong_binary_failure.py index 8c84d436b..65cbca415 100644 --- a/tests/integration/process_wrong_binary_failure/process_wrong_binary_failure.py +++ b/tests/integration/process_wrong_binary_failure/process_wrong_binary_failure.py @@ -29,11 +29,17 @@ def test_process_wrong_binary_failure( results in a failure and triggers the configured recovery action (switch to fallback run target). """ + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/process_wrong_binary_failure.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=6, ) diff --git a/tests/integration/smoke/smoke.py b/tests/integration/smoke/smoke.py index 928827227..4e6237546 100644 --- a/tests/integration/smoke/smoke.py +++ b/tests/integration/smoke/smoke.py @@ -29,11 +29,18 @@ def test_smoke(target, setup_test, assert_test_results, remote_test_dir): The launch manager starts with an initial run target. The control daemon activates the "Running" run target (starting the managed process), then transitions back to "Startup", and finally activates "Off". Expected Behaviour: All run target transitions complete successfully and all processes report running. """ + + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/lifecycle_smoketest.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=3.0, ) diff --git a/tests/integration/switch_run_target/switch_run_target.py b/tests/integration/switch_run_target/switch_run_target.py index 46aff1d05..9f7d2ba88 100644 --- a/tests/integration/switch_run_target/switch_run_target.py +++ b/tests/integration/switch_run_target/switch_run_target.py @@ -34,11 +34,18 @@ def test_switch_run_target(target, setup_test, assert_test_results, remote_test_ The control client activates run_target_a, which depends on run_target_c (containing component_d) and component_a (which depends on component_b). After activation it switches back to Startup and then Off. Expected Behaviour: Component B starts before component A, component D is started, component A terminates before component B, and component E (not in the dependency chain) is never launched. """ + + # launch manager will simply ignore the arguments if run with --//config:use_new_configuration=False. + # the old configuration will be used, which is the default behavior. + # The new configuration will be used if run with --//config:use_new_configuration=True + new_config_path = str(remote_test_dir / "etc/switch_run_target.bin") + run_until_file_deployed( target=target, binary_path=str(remote_test_dir / "launch_manager"), file_path=remote_test_dir.parent / "test_end", cwd=str(remote_test_dir), + args=["-c", new_config_path], timeout_s=2.0, ) From 653b583888ab20ed7a8a9b54ff7944b566a0ef7f Mon Sep 17 00:00:00 2001 From: Paul Quiring Date: Mon, 13 Jul 2026 15:25:13 +0200 Subject: [PATCH 2/2] changed use_new_configuration to visibility public --- config/BUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/BUILD b/config/BUILD index bb03fba94..9805c683a 100644 --- a/config/BUILD +++ b/config/BUILD @@ -72,7 +72,7 @@ config_setting( bool_flag( name = "use_new_configuration", build_setting_default = False, - visibility = ["//:__subpackages__"], + visibility = ["//visibility:public"], ) config_setting( @@ -80,5 +80,5 @@ config_setting( flag_values = { ":use_new_configuration": "True", }, - visibility = ["//:__subpackages__"], + visibility = ["//visibility:public"], )