diff --git a/examples/control_application/control_daemon.cpp b/examples/control_application/control_daemon.cpp index 7468dc2516..e312352b8e 100644 --- a/examples/control_application/control_daemon.cpp +++ b/examples/control_application/control_daemon.cpp @@ -18,7 +18,7 @@ #include "control.hpp" #include "ipc_dropin/socket.hpp" -#include +#include #include std::atomic exitRequested{false}; @@ -41,9 +41,10 @@ int main() return EXIT_FAILURE; } - score::mw::lifecycle::ControlClient client; + auto client_result = score::mw::lifecycle::ILmControl::Create("StateManager/LaunchManager/Instance"); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(client_result.has_value()); + std::unique_ptr client = std::move(client_result).value(); - score::safecpp::Scope<> scope{}; while (!exitRequested) { RunTargetInfo info{}; @@ -52,19 +53,17 @@ int main() std::string runTargetName{info.runTargetName}; std::cout << "Activating Run Target: " << runTargetName << std::endl; - client.ActivateRunTarget(runTargetName).Then({scope, [runTargetName](auto& result) noexcept { - if (!result) - { - std::cerr << "Activating Run Target " << runTargetName - << " failed with error: " - << result.error().Message() << std::endl; - } - else - { - std::cout << "Activating Run Target " << runTargetName - << " succeeded" << std::endl; - } - }}); + const auto result = client->activate_run_target(score::mw::lifecycle::RunTargetName{runTargetName}, true); + if (result.has_value()) + { + + std::cout << "Activating Run Target " << runTargetName << " succeeded" << std::endl; + } + else + { + std::cerr << "Activating Run Target " << runTargetName + << " failed with error: " << result.error().Message() << std::endl; + } } std::this_thread::sleep_for(std::chrono::milliseconds(100)); diff --git a/examples/demo_verification/lifecycle_demo_test.json b/examples/demo_verification/lifecycle_demo_test.json index 0ad84cdb46..b16b8daaaa 100644 --- a/examples/demo_verification/lifecycle_demo_test.json +++ b/examples/demo_verification/lifecycle_demo_test.json @@ -46,7 +46,7 @@ "component_properties": { "binary_name": "control_daemon", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/score/launch_manager/BUILD b/score/launch_manager/BUILD index 55dd202efc..a163a478ef 100644 --- a/score/launch_manager/BUILD +++ b/score/launch_manager/BUILD @@ -18,7 +18,7 @@ package(default_visibility = ["//visibility:public"]) alias( name = "control_cc", - actual = "//score/launch_manager/src/control_client:control_client", + actual = "//score/launch_manager/src/lm_control:lm_control", ) alias( diff --git a/score/launch_manager/docs/user_guide/examples/example_conf.json b/score/launch_manager/docs/user_guide/examples/example_conf.json index ddb3bc7a96..b44663b2d7 100755 --- a/score/launch_manager/docs/user_guide/examples/example_conf.json +++ b/score/launch_manager/docs/user_guide/examples/example_conf.json @@ -114,7 +114,7 @@ "component_properties": { "binary_name": "sm", "application_profile": { - "application_type": "State_Manager" + "application_type": "Reporting_And_Supervised" }, "depends_on": ["setup_filesystem_sh"] }, diff --git a/score/launch_manager/src/control_client/BUILD b/score/launch_manager/src/control_client/BUILD deleted file mode 100644 index 370c6c123c..0000000000 --- a/score/launch_manager/src/control_client/BUILD +++ /dev/null @@ -1,30 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -load("@rules_cc//cc:defs.bzl", "cc_library") - -cc_library( - name = "control_client", - srcs = [ - "src/control_client.cpp", - ], - hdrs = [ - "src/control_client.h", - ], - include_prefix = "score/mw/lifecycle", - strip_include_prefix = "/score/launch_manager/src/control_client/src", - visibility = ["//score:__subpackages__"], - deps = [ - "//score/launch_manager/src/control_client/src/details:control_client_impl", - "//score/launch_manager/src/daemon/src/common:identifier_hash", - ], -) diff --git a/score/launch_manager/src/control_client/src/control_client.cpp b/score/launch_manager/src/control_client/src/control_client.cpp deleted file mode 100644 index c9a050b989..0000000000 --- a/score/launch_manager/src/control_client/src/control_client.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/******************************************************************************** - * 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/concurrency/future/interruptible_future.h" -#include "score/concurrency/future/interruptible_promise.h" - -#include "score/mw/launch_manager/common/identifier_hash.hpp" -#include "score/mw/lifecycle/control_client/details/control_client_impl.hpp" -#include "score/mw/lifecycle/execution_error_event.h" - -#include "control_client.h" - -namespace score::mw::lifecycle -{ - -namespace -{ -// coverity[exn_spec_violation:FALSE] SetError cannot raise an exception in this instance -score::concurrency::InterruptibleFuture GetErrorFuture(ExecErrc errType) noexcept -{ - score::concurrency::InterruptiblePromise tmp_{}; - tmp_.SetError(errType); - return tmp_.GetInterruptibleFuture().value(); -} -} // namespace - -ControlClient::ControlClient() noexcept -{ - static std::function undefinedStateCallback = - []([[maybe_unused]] const score::mw::lifecycle::ExecutionErrorEvent& event) { - }; - - try - { - control_client_impl_ = std::make_unique(undefinedStateCallback); - } - catch (...) - { - control_client_impl_ = nullptr; - } -} - -ControlClient::~ControlClient() noexcept -{ - control_client_impl_.reset(); -} - -ControlClient::ControlClient(ControlClient&& rval) noexcept -{ - control_client_impl_ = std::move(rval.control_client_impl_); - rval.control_client_impl_ = nullptr; -} - -ControlClient& ControlClient::operator=(ControlClient&& rval) noexcept = default; - -score::concurrency::InterruptibleFuture ControlClient::ActivateRunTarget( - std::string_view runTargetName) const noexcept -{ - score::concurrency::InterruptibleFuture retVal_{}; - - if (control_client_impl_ != nullptr) - { - static score::mw::lifecycle::IdentifierHash pg_name{"MainPG"}; - score::mw::lifecycle::IdentifierHash pg_state{runTargetName}; - retVal_ = control_client_impl_->SetState(pg_name, pg_state); - } - else - { - retVal_ = GetErrorFuture(ExecErrc::kCommunicationError); - } - - return retVal_; -} - -} // namespace score::mw::lifecycle diff --git a/score/launch_manager/src/control_client/src/control_client.h b/score/launch_manager/src/control_client/src/control_client.h deleted file mode 100644 index 6f90a3fbd4..0000000000 --- a/score/launch_manager/src/control_client/src/control_client.h +++ /dev/null @@ -1,101 +0,0 @@ -/******************************************************************************** - * 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 - ********************************************************************************/ -#ifndef CONTROL_CLIENT_H_ -#define CONTROL_CLIENT_H_ - -#include -#include - -#include "score/concurrency/future/interruptible_future.h" -#include "score/concurrency/future/interruptible_promise.h" -#include "score/result/result.h" - -namespace score::mw::lifecycle -{ - -class ControlClientImpl; - -/// @brief Class representing connection to Launch Manager that is used to request Run Target activation (or other -/// operations). -/// @note ControlClient opens communication channel to Launch Manager (e.g. POSIX FIFO). Each Process that intends to -/// perform state management, shall create an instance of this class and it shall have rights to use it. -/// -class ControlClient final -{ - public: - /// @brief Constructor that creates Control Client instance. - /// - explicit ControlClient() noexcept; - - /// @brief Destructor of the Control Client instance. - /// @param None. - /// - ~ControlClient() noexcept; - - // Applying the rule of five - // Class will not be copyable, but it will be movable - - /// @brief Suppress default copy construction for ControlClient. - ControlClient(const ControlClient&) = delete; - - /// @brief Suppress default copy assignment for ControlClient. - ControlClient& operator=(const ControlClient&) = delete; - - /// @brief Intentional use of default move constructor for ControlClient. - /// - /// @param[in] rval reference to move - ControlClient(ControlClient&& rval) noexcept; - - /// @brief Intentional use of default move assignment for ControlClient. - /// - /// @param[in] rval reference to move - /// @returns the new reference - ControlClient& operator=(ControlClient&& rval) noexcept; - - /// @brief Method to request activation of a specific Run Target. - /// - /// This method will request Launch Manager to activate a Run Target and return immediately. - /// Returned InterruptibleFuture can be used to determine result of the activation request. - /// - /// @param[in] runTargetName name of the Run Target that should be activated. Launch Manager will deactivate the - /// currently active Run Target and activate Run Target identified by this parameter. - /// @returns void if activation requested is successful, otherwise it returns ExecErrorDomain error. - /// @error score::mw::lifecycle::ExecErrc::kCancelled if activation requested was cancelled by a newer request - /// @error score::mw::lifecycle::ExecErrc::kFailed if activation requested failed - /// @error score::mw::lifecycle::ExecErrc::kFailedUnexpectedTerminationOnExit if Unexpected Termination of a Process - /// assigned to the previously active Run Target happened. - /// @error score::mw::lifecycle::ExecErrc::kFailedUnexpectedTerminationOnEnter if Unexpected Termination of a - /// Process assigned the requested Run Target happened. - /// @error score::mw::lifecycle::ExecErrc::kInvalidArguments if argument passed doesn't appear to be valid (e.g. - /// after a software update, given Run Target doesn't exist anymore) - /// @error score::mw::lifecycle::ExecErrc::kCommunicationError if ControlClient can't communicate with Launch - /// Manager (e.g. IPC link is down) - /// @error score::mw::lifecycle::ExecErrc::kAlreadyInState if the requested Run Target is already active - /// @error score::mw::lifecycle::ExecErrc::kInTransitionToSameState if there is already an ongoing request to - /// activate requested Run Target - /// @error score::mw::lifecycle::ExecErrc::kInvalidTransition if activation of the requested Run Target is - /// prohibited (e.g. Off Run Target) - /// @error score::mw::lifecycle::ExecErrc::kGeneralError if any other error occurs. - /// - /// @threadsafety{thread-safe} - /// - score::concurrency::InterruptibleFuture ActivateRunTarget(std::string_view runTargetName) const noexcept; - - private: - /// @brief Pointer to implementation (Pimpl), we use this pattern to provide ABI compatibility. - std::unique_ptr control_client_impl_; -}; - -} // namespace score::mw::lifecycle - -#endif // CONTROL_CLIENT_H_ diff --git a/score/launch_manager/src/control_client/src/details/BUILD b/score/launch_manager/src/control_client/src/details/BUILD deleted file mode 100644 index 9f1fdf825c..0000000000 --- a/score/launch_manager/src/control_client/src/details/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -load("@rules_cc//cc:defs.bzl", "cc_library") - -cc_library( - name = "control_client_impl", - srcs = [ - "control_client_impl.cpp", - ], - hdrs = [ - "control_client_impl.hpp", - ], - include_prefix = "score/mw/lifecycle/control_client/details", - strip_include_prefix = "/score/launch_manager/src/control_client/src/details", - visibility = ["//score/launch_manager/src/control_client:__subpackages__"], - deps = [ - "//score/launch_manager:error_event", - "//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/control:control_client_channel", - "//score/launch_manager/src/daemon/src/osal:semaphore", - "@score_baselibs//score/concurrency/future", - ], -) diff --git a/score/launch_manager/src/control_client/src/details/control_client_impl.cpp b/score/launch_manager/src/control_client/src/details/control_client_impl.cpp deleted file mode 100644 index 6de12ca1aa..0000000000 --- a/score/launch_manager/src/control_client/src/details/control_client_impl.cpp +++ /dev/null @@ -1,450 +0,0 @@ -/******************************************************************************** - * 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 -#include -#include -#include -#include - -#include - -#include "score/concurrency/future/interruptible_future.h" -#include "score/concurrency/future/interruptible_promise.h" - -#include "control_client_impl.hpp" -#include "score/mw/launch_manager/common/identifier_hash.hpp" -#include "score/mw/launch_manager/common/log.hpp" - -// setting the mapping for both ControlClientCode and ExecErrc codes for error handling -// This approach is used to avoid using switch-case statements -// RULECHECKER_comment(1, 2, check_static_object_dynamic_initialization, "Map doesn't rely on any other static so this -// is fine", false) -static std::map scErrorMap = { - {score::mw::lifecycle::internal::ControlClientCode::kSetStateInvalidArguments, - score::mw::lifecycle::ExecErrc::kInvalidArguments}, - {score::mw::lifecycle::internal::ControlClientCode::kSetStateCancelled, score::mw::lifecycle::ExecErrc::kCancelled}, - {score::mw::lifecycle::internal::ControlClientCode::kSetStateFailed, score::mw::lifecycle::ExecErrc::kFailed}, - {score::mw::lifecycle::internal::ControlClientCode::kSetStateAlreadyInState, - score::mw::lifecycle::ExecErrc::kAlreadyInState}, - {score::mw::lifecycle::internal::ControlClientCode::kSetStateTransitionToSameState, - score::mw::lifecycle::ExecErrc::kInTransitionToSameState}, - {score::mw::lifecycle::internal::ControlClientCode::kFailedUnexpectedTerminationOnEnter, - score::mw::lifecycle::ExecErrc::kFailedUnexpectedTerminationOnEnter}}; - -namespace score::mw::lifecycle -{ - -namespace -{ -// coverity[exn_spec_violation:FALSE] SetError cannot raise an exception in this instance -score::concurrency::InterruptibleFuture GetErrorFuture(score::mw::lifecycle::ExecErrc errType) noexcept -{ - score::concurrency::InterruptiblePromise tmp_{}; - tmp_.SetError(errType); - return tmp_.GetInterruptibleFuture().value(); -} -} // namespace - -bool ControlClientImpl::instance_created_{false}; -std::mutex ControlClientImpl::instance_creation_mutex_{}; - -ControlClientImpl::ControlClientImpl( - std::function undefinedStateCallback) noexcept - : undefined_state_callback_{undefinedStateCallback}, - control_client_requests_{}, - ipc_request_semaphore_{}, - ipc_response_thread_(nullptr), - ipc_response_thread_running_{true}, - ipc_channel_{nullptr} -{ - - std::unique_lock lock(instance_creation_mutex_); - if (instance_created_) - { - LM_LOG_ERROR() << "[Control Client] Only one instance of ControlClient is allowed per process."; - std::abort(); - } - else - { - instance_created_ = true; - } - - struct stat stats; - const auto fstat_ret = fstat(score::mw::lifecycle::internal::osal::IpcCommsSync::sync_fd, &stats); - // Check size we have access of to avoid a crash if fd is not pointing to correct data - const auto needed_size = sizeof(score::mw::lifecycle::internal::osal::IpcCommsSync) + - sizeof(score::mw::lifecycle::internal::ControlClientChannel); - if (fstat_ret == -1 || stats.st_size != static_cast(needed_size)) - { - LM_LOG_ERROR() << "Control client channel at sync_fd is not valid!"; - instance_created_ = false; - std::abort(); - } - - // initialization of control_client_requests_ is a bit more complicated... - // std::atomic_bool is not copyable so we can't use fill method, - // we will need to do this by hand - for (uint16_t i = 0U; i < control_client_requests_.size(); ++i) - { - // promise_ from default constructor is good enough, so no need to change anything - control_client_requests_[i].in_use_ = false; - control_client_requests_[i].initial_machine_state_transition_request_ = false; - } - - ipc_channel_ = score::mw::lifecycle::internal::ControlClientChannel::initializeControlClientChannel(); - - const auto init_result = ipc_request_semaphore_.init(1U, false); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( - score::mw::lifecycle::internal::osal::OsalReturnType::kSuccess == init_result, - "ControlClient semaphore initialization failed"); - ipc_response_thread_ = std::make_unique(&ControlClientImpl::run, this); -} - -ControlClientImpl::~ControlClientImpl() noexcept -{ - std::unique_lock lock(instance_creation_mutex_); - instance_created_ = false; - ipc_response_thread_running_ = false; - - if (ipc_response_thread_->joinable()) - { - ipc_response_thread_->join(); - } - - static_cast(ipc_request_semaphore_.deinit()); -} - -void ControlClientImpl::run() -{ - // creating a instance called msg for ControlClientMessage that will handle all the communication between LCM and - // ControlClientImpl - score::mw::lifecycle::internal::ControlClientMessage msg; - - // This lambda function will be used to set the error of the promise. - // This lamdba funcitons are used to avoid code duplication. - auto funcSetError = [&]() { - control_client_requests_[msg.originating_control_client_.future_id_].promise_.SetError( - scErrorMap[msg.request_or_response_]); - control_client_requests_[msg.originating_control_client_.future_id_].in_use_ = false; - }; - - // This lambda function will be used to set the value of the promise. - auto funcSetValue = [&]() { - control_client_requests_[msg.originating_control_client_.future_id_].promise_.SetValue(); - control_client_requests_[msg.originating_control_client_.future_id_].in_use_ = false; - }; - - // This lambda function will be used to set the error of the promise at unexpected termination. - auto funcUtermination = [&]() { - score::mw::lifecycle::ExecutionErrorEvent tmp{ - msg.execution_error_code_, // executionError - msg.process_group_state_.pg_name_}; // processGroup - - undefined_state_callback_(tmp); - }; - - // This lambda function will be used to set the Notset and failed of the promise at state machine wrong or else - // failure. - std::function funcMcStateWrong = [&]() { - // we need to fulfill all active requests - for (uint16_t i = 0U; i < control_client_requests_.size(); ++i) - { - if ((true == control_client_requests_[i].in_use_) && - (true == control_client_requests_[i].initial_machine_state_transition_request_)) - { - control_client_requests_[i].promise_.SetError(score::mw::lifecycle::ExecErrc::kFailed); - control_client_requests_[i].initial_machine_state_transition_request_ = false; - control_client_requests_[i].in_use_ = false; - } - } - }; - - // This lambda function will be used to set the kInitialMachineStateSuccess of the promise at state machine success. - std::function funcMcStateSuccess = [&]() { - // we need to fulfill all active requests - for (uint16_t i = 0U; i < control_client_requests_.size(); ++i) - { - if ((true == control_client_requests_[i].in_use_) && - (true == control_client_requests_[i].initial_machine_state_transition_request_)) - { - control_client_requests_[i].promise_.SetValue(); - control_client_requests_[i].initial_machine_state_transition_request_ = false; - control_client_requests_[i].in_use_ = false; - } - } - }; - - // This lambda function will be used to set the error of the promise at default error for ControlClientCode kNotSet. - std::function funcDefaultError = [&]() { - if (msg.request_or_response_ != score::mw::lifecycle::internal::ControlClientCode::kNotSet) - { - LM_LOG_WARN() << "ControlClient error. Undefined message from Launch Manager:" - << static_cast(msg.request_or_response_); - } - }; - - // there is no point for this thread to exist if there is no communication with LCM - // in that case, we just return from the function - if (nullptr != ipc_channel_) - { - while (ipc_response_thread_running_) - { - if (ipc_channel_->getResponse(msg)) - { - switch (msg.request_or_response_) - { - case score::mw::lifecycle::internal::ControlClientCode::kSetStateInvalidArguments: - case score::mw::lifecycle::internal::ControlClientCode::kSetStateCancelled: - case score::mw::lifecycle::internal::ControlClientCode::kSetStateFailed: - case score::mw::lifecycle::internal::ControlClientCode::kSetStateAlreadyInState: - case score::mw::lifecycle::internal::ControlClientCode::kSetStateTransitionToSameState: - case score::mw::lifecycle::internal::ControlClientCode::kFailedUnexpectedTerminationOnEnter: - funcSetError(); - break; - - case score::mw::lifecycle::internal::ControlClientCode::kSetStateSuccess: - funcSetValue(); - break; - - case score::mw::lifecycle::internal::ControlClientCode::kFailedUnexpectedTermination: - funcUtermination(); - break; - - case score::mw::lifecycle::internal::ControlClientCode::kInitialMachineStateNotSet: - case score::mw::lifecycle::internal::ControlClientCode::kInitialMachineStateFailed: - funcMcStateWrong(); - break; - - case score::mw::lifecycle::internal::ControlClientCode::kInitialMachineStateSuccess: - funcMcStateSuccess(); - break; - - default: - // score::mw::lifecycle::internal::ControlClientCode::kNotSet is just an initialization value - // not an error - funcDefaultError(); - break; - } - } - - std::this_thread::sleep_for(score::mw::lifecycle::internal::kControlClientBgThreadSleepTime); - } - } -} - -score::concurrency::InterruptibleFuture ControlClientImpl::SendIpcMessage( - score::mw::lifecycle::internal::ControlClientMessage& msg) noexcept -{ - score::concurrency::InterruptibleFuture retVal_{}; - - if (score::mw::lifecycle::internal::osal::OsalReturnType::kSuccess == - ipc_request_semaphore_.timedWait(score::mw::lifecycle::internal::kControlClientMaxIpcDelay)) - { - // first we need to check if we have empty space in control_client_requests_ array - uint16_t i = 0U; - - for (; i < control_client_requests_.size(); ++i) - { - bool expected = false; - if (control_client_requests_[i].in_use_.compare_exchange_strong(expected, true)) - { - break; - } - } - - if (i < control_client_requests_.size()) - { - // we have empty slot so... - // 1) claim the slot and create a fresh promise for this request - control_client_requests_[i].promise_ = score::concurrency::InterruptiblePromise{}; - - if (score::mw::lifecycle::internal::ControlClientCode::kGetInitialMachineStateRequest == - msg.request_or_response_) - { - // the GetInitialMachineStateTransitionResult request is a bit special - // and will need special treatment in bg thread servicing response_ link - control_client_requests_[i].initial_machine_state_transition_request_ = true; - } - - // 2) save promise index - msg.originating_control_client_.future_id_ = i; - - // 3) get the future - retVal_ = control_client_requests_[i].promise_.GetInterruptibleFuture().value(); - - // 4) finally we can send the message as we are done with control_client_requests_ - ipc_channel_->sendRequest(msg); - - // 5) check the response. For errors we can get the response immediately - auto it = scErrorMap.find(msg.request_or_response_); - if (it != scErrorMap.end()) - { - control_client_requests_[i].promise_.SetError(it->second); - control_client_requests_[i].in_use_ = false; - } - } - else - { - // no empty space for new request - retVal_ = GetErrorFuture(ExecErrc::kFailed); - } - - // we definitely shouldn't forget to release semaphore - const auto post_result = ipc_request_semaphore_.post(); - if (score::mw::lifecycle::internal::osal::OsalReturnType::kSuccess != post_result) - { - // Invalid semaphore usage is a logic error and should be asserted. - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( - EINVAL != errno, "ControlClient semaphore post() failed: invalid semaphore (EINVAL)"); - - if (EOVERFLOW == errno) - { - LM_LOG_ERROR() << "ControlClient semaphore post() failed with EOVERFLOW; possible stuck consumer in " - "Launch Manager"; - } - else - { - LM_LOG_ERROR() << "ControlClient semaphore post() failed with errno=" << errno; - } - } - } - else - { - retVal_ = GetErrorFuture(ExecErrc::kCommunicationError); - } - - return retVal_; -} - -score::concurrency::InterruptibleFuture ControlClientImpl::SetState( - const score::mw::lifecycle::IdentifierHash& pg_name, - const score::mw::lifecycle::IdentifierHash& pg_state) noexcept -{ - score::concurrency::InterruptibleFuture retVal_{}; - - if (nullptr != ipc_channel_) - { - score::mw::lifecycle::internal::ControlClientMessage msg; - - msg.request_or_response_ = score::mw::lifecycle::internal::ControlClientCode::kSetStateRequest; - msg.process_group_state_.pg_name_ = pg_name; - msg.process_group_state_.pg_state_name_ = pg_state; - - retVal_ = SendIpcMessage(msg); - } - else - { - retVal_ = GetErrorFuture(ExecErrc::kCommunicationError); - } - - return retVal_; -} - -score::concurrency::InterruptibleFuture ControlClientImpl::GetInitialMachineStateTransitionResult() noexcept -{ - score::concurrency::InterruptibleFuture retVal_{}; - - if (nullptr != ipc_channel_) - { - score::mw::lifecycle::internal::ControlClientMessage msg; - - msg.request_or_response_ = score::mw::lifecycle::internal::ControlClientCode::kGetInitialMachineStateRequest; - // pg_name_ is not used by this request - // pg_state_name_ is not used by this request - - retVal_ = SendIpcMessage(msg); - } - else - { - retVal_ = GetErrorFuture(ExecErrc::kCommunicationError); - } - - return retVal_; -} - -score::Result ControlClientImpl::GetExecutionError( - const score::mw::lifecycle::IdentifierHash& processGroup) noexcept -{ - // default error (just in case) - score::Result retVal_{ - score::MakeUnexpected(score::mw::lifecycle::ExecErrc::kCommunicationError)}; - - if (nullptr != ipc_channel_) - { - if (score::mw::lifecycle::internal::osal::OsalReturnType::kSuccess == - ipc_request_semaphore_.timedWait(score::mw::lifecycle::internal::kControlClientMaxIpcDelay)) - { - // 1) prepare message for LCM - score::mw::lifecycle::internal::ControlClientMessage msg; - - // future_id_ is not used by this request - msg.request_or_response_ = score::mw::lifecycle::internal::ControlClientCode::kGetExecutionErrorRequest; - msg.process_group_state_.pg_name_ = processGroup; - // pg_state_name_ is not used by this request - - // 2) send the message - ipc_channel_->sendRequest(msg); - - // 3) process the response from LCM as kGetExecutionErrorRequest is a synchronous call - switch (msg.request_or_response_) - { - // GetExecutionError - case score::mw::lifecycle::internal::ControlClientCode::kExecutionErrorInvalidArguments: - case score::mw::lifecycle::internal::ControlClientCode::kExecutionErrorRequestFailed: - retVal_ = score::MakeUnexpected(score::mw::lifecycle::ExecErrc::kFailed); - break; - - case score::mw::lifecycle::internal::ControlClientCode::kExecutionErrorRequestSuccess: - { - score::mw::lifecycle::ExecutionErrorEvent tmp{ - msg.execution_error_code_, // executionError - msg.process_group_state_.pg_name_}; // processGroup - retVal_.emplace(std::move(tmp)); - } - break; - - default: - LM_LOG_WARN() << "ControlClient error. GetExecutionError unexpected response from Launch Manager:" - << static_cast(msg.request_or_response_); - retVal_ = score::MakeUnexpected(score::mw::lifecycle::ExecErrc::kFailed); - break; - } - - // we definitely shouldn't forget to release semaphore - const auto post_result = ipc_request_semaphore_.post(); - if (score::mw::lifecycle::internal::osal::OsalReturnType::kSuccess != post_result) - { - // Invalid semaphore usage is a logic error and should be asserted. - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( - EINVAL != errno, "ControlClient semaphore post() failed: invalid semaphore (EINVAL)"); - - if (EOVERFLOW == errno) - { - LM_LOG_ERROR() << "ControlClient semaphore post() failed with EOVERFLOW; possible stuck consumer " - "in Launch Manager"; - } - else - { - LM_LOG_ERROR() << "ControlClient semaphore post() failed with errno=" << errno; - } - } - } - // else not needed as kCommunicationError is the default return value - } - // else not needed as kCommunicationError is the default return value - - return retVal_; -} - -} // namespace score::mw::lifecycle diff --git a/score/launch_manager/src/control_client/src/details/control_client_impl.hpp b/score/launch_manager/src/control_client/src/details/control_client_impl.hpp deleted file mode 100644 index d8e21fffd8..0000000000 --- a/score/launch_manager/src/control_client/src/details/control_client_impl.hpp +++ /dev/null @@ -1,234 +0,0 @@ -/******************************************************************************** - * 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 - ********************************************************************************/ - -#ifndef CONTROL_CLIENT_IMPL_H_ -#define CONTROL_CLIENT_IMPL_H_ - -#include -#include -#include - -#include "score/mw/launch_manager/common/constants.hpp" -#include "score/mw/launch_manager/common/identifier_hash.hpp" -#include "score/mw/launch_manager/control/control_client_channel.hpp" -#include "score/mw/launch_manager/osal/semaphore.hpp" -#include "score/mw/lifecycle/execution_error.h" -#include "score/mw/lifecycle/execution_error_event.h" - -namespace score::mw::lifecycle -{ - -/// @brief Data structure used to manage active, i.e. still not completed, requests from ControlClient. -/// Most of the ControlClient methods are asynchronous. This means that when an API call returns, -/// the request was communicated to LCM and LCM agreed to work on it. The result of that request usually -/// arrives later, so ControlClientImpl needs a storage where those active requests can wait for completion. -struct ControlClientRequestInfo -{ - score::concurrency::InterruptiblePromise - promise_; ///< promise that should be fulfilled, i.e. set_value(), when answer from LCM is available. - ///< SetState() and GetInitialMachineStateTransitionResult() use this type. - std::atomic_bool in_use_; ///< information whether this slot in the array is used or not. We are using atomic flag - ///< as only the code that reserves the slot will use synchronization primitive. There is - ///< no reason to protect release code, thanks to the std::atomic_flag - bool initial_machine_state_transition_request_; ///< is this a request that originated from - ///< GetInitialMachineStateTransitionResult? Due to the design of - ///< LCM, there is no place to cache those request on LCM side. As - ///< there is no limit on how many times ControlClient instance can - ///< call GetInitialMachineStateTransitionResult method, we will - ///< cache them on ControlClientImpl side. - // Constructor to initialize all members - ControlClientRequestInfo() : promise_(), in_use_(false), initial_machine_state_transition_request_(false) - { - } -}; - -/// @brief Class to encapsulate LCM implementation details of Control Client. -/// This class exist to provide ABI compatibility for ControlClient, which is an AUTOSAR defined interface. -/// -/// Why this class exist? -/// * To hide (encapsulate) implementation details of ControlClient class, from users. -/// - Please note that we don't have to publish any details of communication channel (control_client_channel.hpp). -/// -/// * To provide ABI compatibility for ControlClient. -/// -/// * To provide access to the ControlClientChannel (IPC link to LCM daemon). -/// - The request_ link will be managed through function calls from SM and access will be protected by -/// a semaphore. When a request is successfully communicated to LCM, a promise will be stored inside -/// ControlClientImpl and a future, obtained from that promise, will be returned to the caller. -/// -/// - The response_ link will be managed by background thread. This thread will read responses -/// and pass them to the corresponding promises, so they could be received through a future inside SM. -/// -/// - This class is essentially a singleton, we can only have one thread that manages the response_ link. -/// For this reason factory pattern will be deployed and ControlClient instances will only hold -/// shared pointers to this class. -class ControlClientImpl final -{ - public: - ControlClientImpl() = delete; - - ControlClientImpl( - std::function undefinedStateCallback) noexcept; - - // this class is not movable or copyable by definition - ControlClientImpl(const ControlClientImpl&) = delete; - - ControlClientImpl& operator=(const ControlClientImpl&) = delete; - - ControlClientImpl(ControlClientImpl&& rval) = delete; - - ControlClientImpl& operator=(ControlClientImpl&& rval) = delete; - - /// @brief Method to request state transition for a single Process Group. - /// - /// This method will request Launch Manager to perform state transition and return immediately. - /// Returned InterruptibleFuture can be used to determine result of requested transition. - /// - /// - /// @param[in] pg_name representing meta-model definition of a specific Process Group - /// @param[in] pg_state representing meta-model definition of a state. Launch Manager will perform state transition - /// from the current state to the state identified by this parameter. - /// - /// @returns void if requested transition is successful, otherwise it returns ExecErrorDomain error. - /// @error score::mw::lifecycle::ExecErrc::kCancelled if transition to the requested Process Group state was - /// cancelled by a newer request - /// @error score::mw::lifecycle::ExecErrc::kFailed if transition to the requested Process Group state failed - /// @error score::mw::lifecycle::ExecErrc::kFailedUnexpectedTerminationOnExit if Unexpected Termination in Process - /// of previous Process Group State happened. - /// @error score::mw::lifecycle::ExecErrc::kFailedUnexpectedTerminationOnEnter if Unexpected Termination in Process - /// of target Process Group State happened. - /// @error score::mw::lifecycle::ExecErrc::kInvalidArguments if arguments passed doesn't appear to be valid (e.g. - /// after a software update, given processGroup doesn't exist anymore) - /// @error score::mw::lifecycle::ExecErrc::kCommunicationError if ControlClient can't communicate with Launch - /// Manager (e.g. IPC link is down) - /// @error score::mw::lifecycle::ExecErrc::kAlreadyInState if the ProcessGroup is already in the requested state - /// @error score::mw::lifecycle::ExecErrc::kInTransitionToSameState if a transition to the requested state is - /// already ongoing - /// @error score::mw::lifecycle::ExecErrc::kInvalidTransition if transition to the requested state is prohibited - /// (e.g. Off state for MainPG) - /// @error score::mw::lifecycle::ExecErrc::kGeneralError if any other error occurs. - score::concurrency::InterruptibleFuture SetState( - const score::mw::lifecycle::IdentifierHash& pg_name, - const score::mw::lifecycle::IdentifierHash& pg_state) noexcept; - - /// @brief Method to retrieve result of Machine State initial transition to Startup state. - /// - /// Please note that this transition happens once per machine life cycle, thus result delivered by this method shall - /// not change (unless machine is started again). - /// - /// @returns void if requested transition is successful, otherwise it returns ExecErrorDomain error. - /// @error score::mw::lifecycle::ExecErrc::kCancelled if transition to the requested Process Group state was - /// cancelled by a newer request - /// @error score::mw::lifecycle::ExecErrc::kFailed if transition to the requested Process Group state failed - /// @error score::mw::lifecycle::ExecErrc::kCommunicationError if ControlClient can't communicate with Launch - /// Manager (e.g. IPC link is down) - /// @error score::mw::lifecycle::ExecErrc::kGeneralError if any other error occurs. - score::concurrency::InterruptibleFuture GetInitialMachineStateTransitionResult() noexcept; - - /// @brief Returns the execution error which changed the given Process Group to an Undefined Process Group State. - /// - /// This function will return with error and will not return an ExecutionErrorEvent object, if the given - /// Process Group is in a defined Process Group state again. - /// - /// @param[in] processGroup For which Process Group the error should be retrieved. - /// - /// @returns The execution error which changed the given Process Group to an Undefined Process Group State. - /// @error score::mw::lifecycle::ExecErrc::kFailed Given Process Group is not in an Undefined Process Group - /// State. - /// @error score::mw::lifecycle::ExecErrc::kCommunicationError if ControlClient can't communicate with Launch - /// Manager (e.g. IPC link is down) - score::Result GetExecutionError( - const score::mw::lifecycle::IdentifierHash& processGroup) noexcept; - - ~ControlClientImpl() noexcept; - - private: - /// @brief Flag to indicate whether an instance of ControlClient has already been created. - /// Only one instance per process is allowed. If a second instance is created, the application - /// will be aborted. - static bool instance_created_; - - /// @brief Protect instance creation in multi-threaded scenarios. - static std::mutex instance_creation_mutex_; - - /// @brief callback that ControlClient instance ask us to invoke when there is a problem with PG - std::function undefined_state_callback_; - - /// @brief Array of active requests, that wait for completion from LCM side. - /// When a request has been send to LCM and the answer is not immediately available, - /// ControlClientImpl classify such a request as an active or ongoing request. - /// A promise for such a request is stored in this array and is fulfilled when - /// answer arrives from LCM. - std::array< - ControlClientRequestInfo, - static_cast(score::mw::lifecycle::internal::ControlClientLimits::kControlClientMaxRequests)> - control_client_requests_; - - /// @brief Semaphore used to protect access to the request_ link of ControlClientChannel, - /// as well as control_client_requests_. - /// Access to the request_ link needs to be protected as, this link only support a single request - /// at a time and LCM acceptance of the request needs to be retrieved. - /// Please note that synchronization for control_client_requests_ is only needed, when we are booking a slot - /// inside this array. When we are releasing a slot inside this array, this can be done without - /// ipc_request_semaphore_ protection. - score::mw::lifecycle::internal::osal::Semaphore ipc_request_semaphore_; - - /// @brief Thread used for monitoring response_ link of ControlClientChannel. - /// Asynchronous nature of ControlClient API means responses to ControlClient requests, will arrive at - /// a random point in future. For this reason a background thread is needed to monitor response_ link, - /// this way we can deliver answers when they arrive from LCM. - std::unique_ptr ipc_response_thread_; - - /// @brief Synchronization variable used to manage lifetime of ipc_response_thread_ - /// As long as ipc_response_thread_running_ is set to true, - /// the ipc_response_thread_ should stay alive and perform its job. - /// When ipc_response_thread_running_ is set to false, - /// the ipc_response_thread_ should finish its execution and exit ASAP. - std::atomic_bool ipc_response_thread_running_; - - /// @brief Entry point for ipc_response_thread_ - /// This code will run in background and perform the work that is needed. - /// Exit from this function depends on ipc_response_thread_running_ variable. - void run(); - - /// @brief Handle to the real IPC communication channel with LCM - /// This handle is used to perform low level communication with LCM. - score::mw::lifecycle::internal::ControlClientChannelP ipc_channel_; - - /// @brief Helper method to send a message to LCM, through IPC link (aka request_ link). - /// - /// This method will check if we have empty slot in control_client_requests_ array. - /// If yes, it will create a fresh promise for this request and send the message. The index of the slot - /// used to store mentioned promise, is saved inside message before it is send. - /// If no, error will be returned. - /// - ///@param[in, out] msg A message that should be send to LCM. - /// If kControlClientMaxRequests is not reached and we have empty slot, - /// this slot index is written into the msg before sending. - /// Otherwise msg.originating_control_client_.future_id_ is not updated and - /// error is returned. - /// - /// @returns score::concurrency::InterruptibleFuture when message is successfully sent to LCM. This future can - /// be - /// used to retrieve LCM response at a later time. - /// @error score::mw::lifecycle::ExecErrc::kFailed if the message could not be send to LCM - /// @error score::mw::lifecycle::ExecErrc::kCommunicationError if we can't get access to the request_ link - /// - /// @threadsafety{thread-safe} - score::concurrency::InterruptibleFuture SendIpcMessage( - score::mw::lifecycle::internal::ControlClientMessage& msg) noexcept; -}; - -} // namespace score::mw::lifecycle - -#endif // CONTROL_CLIENT_IMPL_H_ 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 a3e79e50fc..a64c12fced 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 @@ -22,11 +22,6 @@ #include #include -namespace score::mw::lifecycle -{ -class ControlClient; -} // namespace score::mw::lifecycle - namespace score::mw::lifecycle::internal::saf::factory { diff --git a/score/launch_manager/src/daemon/src/common/constants.hpp b/score/launch_manager/src/daemon/src/common/constants.hpp index 4f87dea065..b7e014fdf8 100644 --- a/score/launch_manager/src/daemon/src/common/constants.hpp +++ b/score/launch_manager/src/daemon/src/common/constants.hpp @@ -37,34 +37,15 @@ constexpr std::chrono::milliseconds kMaxQueueDelay{ constexpr std::chrono::milliseconds kGraphTimeout{10000}; ///< Timeout duration for graph operations. constexpr std::chrono::milliseconds kMaxSigKillDelay{500}; ///< The maximum time to wait for a process termination -constexpr std::chrono::milliseconds kControlClientPollingDelay{ - 1}; ///< Time Control Client will wait during polling for acknowledgement - constexpr std::chrono::milliseconds kMaxRunningDelay{ 1000}; ///< report_running() API will wait for Launch Manager to respond -constexpr std::chrono::milliseconds kControlClientMaxIpcDelay{ - 500}; ///< The maximum time to wait, when trying to communicate with LCM. When this time is exceeded - ///< kCommunicationError will be returned -constexpr std::chrono::milliseconds kControlClientBgThreadSleepTime{100}; - constexpr std::chrono::milliseconds kDefaultOffStateTransitionTimeout{ 3000}; ///< Default timeout for Off state transition constexpr std::int64_t kMainLoopCycleTimeMs{50}; ///< The period at which the main loop services the watchdog constexpr std::int64_t kMainLoopCycleTimeNs{kMainLoopCycleTimeMs * 1'000'000LL}; -enum class ControlClientLimits : uint16_t -{ - kControlClientMaxInstances = 256U, ///< Maximum number of ControlClient instances that should be created by state - ///< manager. If state manager create more instances than kMaxInstances, those - ///< instances will always return kCommunicationError when used - kControlClientMaxRequests = - 512U ///< Maximum number of active requests, for example SetState call, that ControlClient instance can send to - ///< LCM. If that number is exceeded ControlClient API will return kFailed, until one of the current - ///< requests is completed by LCM -}; - enum class ProcessLimits : std::uint32_t { kMaxProcesses = 1024U, ///< Maximum number of processes allowed diff --git a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters.cpp b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters.cpp index 1f5724bbd3..deff558110 100644 --- a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters.cpp +++ b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters.cpp @@ -83,8 +83,6 @@ ApplicationType convertApplicationType(fb::ApplicationType fb_type) return ApplicationType::Reporting; case fb::ApplicationType::Reporting_And_Supervised: return ApplicationType::ReportingAndSupervised; - case fb::ApplicationType::State_Manager: - return ApplicationType::StateManager; case fb::ApplicationType::Native: default: return ApplicationType::Native; diff --git a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters_UT.cpp b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters_UT.cpp index 3591f82cbe..10ddc61e34 100644 --- a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters_UT.cpp +++ b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters_UT.cpp @@ -156,8 +156,7 @@ INSTANTIATE_TEST_SUITE_P( ApplicationTypeTestParam{ fb::ApplicationType::Reporting_And_Supervised, ApplicationType::ReportingAndSupervised, - "ReportingAndSupervised"}, - ApplicationTypeTestParam{fb::ApplicationType::State_Manager, ApplicationType::StateManager, "StateManager"}), + "ReportingAndSupervised"}), [](const ::testing::TestParamInfo& info) { return info.param.name; }); diff --git a/score/launch_manager/src/daemon/src/control/BUILD b/score/launch_manager/src/daemon/src/control/BUILD deleted file mode 100644 index 090ccec7dc..0000000000 --- a/score/launch_manager/src/daemon/src/control/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -load("@rules_cc//cc:defs.bzl", "cc_library") - -cc_library( - name = "control_client_channel", - srcs = [ - "control_client_channel.cpp", - ], - hdrs = [ - "control_client_channel.hpp", - ], - include_prefix = "score/mw/launch_manager/control", - strip_include_prefix = "/score/launch_manager/src/daemon/src/control", - visibility = ["//score:__subpackages__"], - deps = [ - "//score/launch_manager/src/daemon/src/common:constants", - "//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:ipc_comms", - "@score_baselibs//score/language/futurecpp", - ], -) diff --git a/score/launch_manager/src/daemon/src/control/control_client_channel.cpp b/score/launch_manager/src/daemon/src/control/control_client_channel.cpp deleted file mode 100644 index 24620852aa..0000000000 --- a/score/launch_manager/src/daemon/src/control/control_client_channel.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/******************************************************************************** - * 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 -#include -#include - -#include - -#include "control_client_channel.hpp" -#include "score/mw/launch_manager/common/constants.hpp" -#include "score/mw/launch_manager/common/log.hpp" - -namespace score::mw::lifecycle::internal -{ - -void ControlClientChannel::initialize() -{ - request_.empty_.store(true); - response_.empty_.store(true); - - const auto result = nudge_LM_Handler_.init(0U, true); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - result == osal::OsalReturnType::kSuccess, "ControlClientChannel semaphore init failed"); - - initial_result_count_ = 0U; - - LM_LOG_DEBUG() << "ControlClientChannel initialized"; -} - -void ControlClientChannel::deinitialize() -{ - const auto result = nudge_LM_Handler_.deinit(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - result == osal::OsalReturnType::kSuccess, "ControlClientChannel semaphore deinit failed"); -} - -bool ControlClientChannel::sendResponse(ControlClientMessage& msg) -{ - if (!response_.empty_) - { - LM_LOG_DEBUG() << "Failed to send response: response is not empty."; - return false; - } - - response_.msg_ = msg; - response_.empty_ = false; - - const auto result = nudge_LM_Handler_.post(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - result == osal::OsalReturnType::kSuccess, "ControlClientChannel semaphore post failed"); - - LM_LOG_DEBUG() << "Response sent."; - return true; -} - -bool ControlClientChannel::getResponse(ControlClientMessage& msg) -{ - bool result = !response_.empty_; - - if (result) - { - msg = response_.msg_; - response_.empty_ = true; - LM_LOG_DEBUG() << "Response retrieved."; - } - - return result; -} - -void ControlClientChannel::sendRequest(ControlClientMessage& msg) -{ - request_.msg_ = msg; - request_.empty_ = false; - - // now map the semaphore and post on it - // Attempt to map the semaphore - auto* nudgeLM = mmap( - NULL, - sizeof(osal::Semaphore), - PROT_READ | PROT_WRITE, - MAP_SHARED, - osal::IpcCommsSync::control_client_handler_nudge_fd, - 0); - - // RULECHECKER_comment(1, 1, check_c_style_cast, "This is the definition provided by the OS and does a C-style - // cast.", true) - if (nudgeLM != MAP_FAILED) - { - LM_LOG_DEBUG() << "Request sent. Waiting for acknowledgment..."; - auto* semaphore = static_cast(nudgeLM); - - // coverity[cert_mem52_cpp_violation:FALSE] The allocated memory is checked by the containing if statement. - const auto result = semaphore->post(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - result == osal::OsalReturnType::kSuccess, "ControlClientChannel semaphore post failed"); - - munmap(nudgeLM, sizeof(osal::Semaphore)); // Unmap the semaphore - } - - const auto result = nudge_LM_Handler_.wait(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - result == osal::OsalReturnType::kSuccess, "ControlClientChannel semaphore wait failed"); - - // Wait for acknowledgment - while (!request_.empty_) - { - std::this_thread::sleep_for(kControlClientPollingDelay); - } - - msg = request_.msg_; - LM_LOG_DEBUG() << "Request acknowledged."; -} - -bool ControlClientChannel::getRequest() -{ - return !request_.empty_; -} - -ControlClientMessage& ControlClientChannel::request() -{ - return request_.msg_; -} - -void ControlClientChannel::acknowledgeRequest() -{ - const auto result = nudge_LM_Handler_.post(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - result == osal::OsalReturnType::kSuccess, "ControlClientChannel semaphore post failed"); - - request_.empty_ = true; - - LM_LOG_DEBUG() << "Request acknowledged."; -} - -ControlClientChannelP ControlClientChannel::initializeControlClientChannel(int fileDesc, osal::IpcCommsP* mem_ptr) -{ - ControlClientChannelP result = nullptr; - void* channelMemory = mmap( - nullptr, - sizeof(ControlClientChannel) + sizeof(osal::IpcCommsSync), - PROT_READ | PROT_WRITE, - MAP_SHARED, - fileDesc, - 0); - - if (MAP_FAILED == channelMemory) - { - LM_LOG_ERROR() << "mmap failed in initializeControlClientChannel:" << errno_message(errno); - return nullptr; - } - - auto* commsHeader = static_cast(channelMemory); - - if (mem_ptr != nullptr) - { - *mem_ptr = osal::IpcCommsP(commsHeader, [](osal::IpcCommsSync* ptr) { - if (ptr != nullptr) - { - if (munmap(ptr, sizeof(ControlClientChannel) + sizeof(osal::IpcCommsSync)) == -1) - { - LM_LOG_ERROR() << "Unmapping of shared memory (creation path) failed"; - } - } - }); - commsHeader->comms_type_ = osal::CommsType::kControlClient; - } - else - { - if (commsHeader->comms_type_ != osal::CommsType::kControlClient) - { - LM_LOG_ERROR() << "Invalid comms type (" << static_cast(commsHeader->comms_type_) - << ") in initializeControlClientChannel attach path."; - if (munmap(channelMemory, sizeof(ControlClientChannel) + sizeof(osal::IpcCommsSync)) == -1) - { - LM_LOG_ERROR() << "Unmapping after invalid comms type failed"; - } - return nullptr; - } - } - - char* controlClientStartPtr = - std::next(static_cast(channelMemory), static_cast(sizeof(osal::IpcCommsSync))); - result = ControlClientChannelP( - static_cast(static_cast(controlClientStartPtr)), [](ControlClientChannel*) { - }); - if (result) - { - std::unique_lock lock(init_mutex_); - is_initialized_ = true; - lock.unlock(); - init_cv_.notify_all(); - } - return result; -} - -ControlClientChannelP ControlClientChannel::getControlClientChannel(osal::IpcCommsP sync) -{ - ControlClientChannelP result = nullptr; - { - std::unique_lock lock(init_mutex_); - if (!is_initialized_) - { - init_cv_.wait(lock, [] { - return is_initialized_; - }); - } - } - - if (sync && osal::CommsType::kControlClient == sync->comms_type_) - { - auto* syncMemory = sync.get(); - - // Control Client is in shared memory adjacent to ipc comms sync object - char* sharedMemoryPtr = static_cast(static_cast(syncMemory)); - std::ptrdiff_t ipcCommSyncStartPtr = static_cast(sizeof(osal::IpcCommsSync)); - void* controlClientChannelPos = static_cast(std::next(sharedMemoryPtr, ipcCommSyncStartPtr)); - - // coverity[cert_mem56_cpp_violation:INTENTIONAL] Pointer is only owned by one shared pointer. - result = ControlClientChannelP( - static_cast(controlClientChannelPos), [](ControlClientChannel*) { - }); - - result->ipc_parent_ = sync; - LM_LOG_DEBUG() << "ControlClientChannel obtained from sync."; - } - else - { - LM_LOG_ERROR() << "Invalid comms type in getControlClientChannel."; - } - - return result; -} - -void ControlClientChannel::nudgeControlClientHandler() -{ - if (nudgeControlClientHandler_) - { - const auto result = nudgeControlClientHandler_->post(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - result == osal::OsalReturnType::kSuccess, "ControlClientChannel semaphore post failed"); - - LM_LOG_DEBUG() << "Control Client handler nudged"; - } -} - -void ControlClientChannel::nudgeLMHandler() -{ - const auto result = nudge_LM_Handler_.post(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - result == osal::OsalReturnType::kSuccess, "ControlClientChannel semaphore post failed"); -} - -void ControlClientChannel::releaseParentMapping() -{ - ipc_parent_.reset(); -} - -std::string_view ControlClientChannel::toString(ControlClientCode code) -{ - for (const auto& mapping : stateArray) - { - if (mapping.code == code) - { - return mapping.description; - } - } - - return "Unknown ControlClientCode"; -} - -osal::Semaphore* ControlClientChannel::nudgeControlClientHandler_ = nullptr; -bool ControlClientChannel::is_initialized_ = false; -std::condition_variable ControlClientChannel::init_cv_{}; -std::mutex ControlClientChannel::init_mutex_{}; - -} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/control/control_client_channel.hpp b/score/launch_manager/src/daemon/src/control/control_client_channel.hpp deleted file mode 100644 index 5005c86329..0000000000 --- a/score/launch_manager/src/daemon/src/control/control_client_channel.hpp +++ /dev/null @@ -1,299 +0,0 @@ -/******************************************************************************** - * 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 - ********************************************************************************/ - -#ifndef CONTROL_CLIENT_CHANNEL_HPP_INCLUDED -#define CONTROL_CLIENT_CHANNEL_HPP_INCLUDED - -#include -#include -#include -#include - -#include "score/mw/launch_manager/common/process_group_state_id.hpp" -#include "score/mw/launch_manager/osal/ipc_comms.hpp" - -namespace score::mw::lifecycle::internal -{ - -/// @brief This is initially some ID provided by the Control Client library. When received -/// by Control Client handler additional information is added - the state manager -/// process originating the request. This can be given in the form of a function -/// group index and process identifier. -/// When the Control Client library receives a response, it must be able to extract -/// the client ID, ignoring the state manager process identification. -struct ControlClientID final -{ - uint16_t process_group_index_; ///< Process group containing the state manager process - IdentifierHash process_identifier_; ///< The process within the process group - uint32_t future_id_; ///< ID to match request and response - ControlClientID() : process_group_index_(0), process_identifier_(""), future_id_(0) - { - } ///< For use by Control Client -}; - -/// @brief Code for requests from Control Client and responses back from Launch Manager -enum class ControlClientCode // Both request and response codes are given. Mapping to AUTOSAR is done at client side -{ - // General - kNotSet = 0, ///< This code is used to initialise variables - kInvalidRequest = 1, ///< Response back to Control Client if Launch Manager gets a code it does not recognise - - // setState functionality - kSetStateRequest = 16, ///< setState request code - kSetStateInvalidArguments = 17, ///< Response: invalid arguments response for set state request - kSetStateCancelled = 18, ///< Response: setState request was cancelled by a newer request - kSetStateFailed = 19, ///< Response: setState request failed (PG in undefined state) - kSetStateSuccess = 20, ///< Response: setState request succeeded (PG in new state) - kSetStateAlreadyInState = 21, ///< Response: setState had no effect because PG was already in requested state - kSetStateTransitionToSameState = - 22, ///< Response: setState had no effect because PG already in transition to new state - - // Responses resulting from unexpected termination (when do we report these?) - kFailedUnexpectedTerminationOnEnter = - 23, ///< Response: Unexpected Termination of a process during transition to new process group state - kFailedUnexpectedTermination = 24, ///< Response: termination of a process when not in transition - - // getInitialMachineState functionality - kGetInitialMachineStateRequest = 32, ///< Request the initial machine state result - kInitialMachineStateNotSet = 33, ///< Internal value used before first state transition or there is no machine PG - kInitialMachineStateFailed = - 34, ///< Response: The transition to the initial machine state failed (or was cancelled) - kInitialMachineStateSuccess = 35, ///< Response: The initial machine state transition was successful - - // getExecutionError functionality - kGetExecutionErrorRequest = 48, ///< Request the execution error for a process group - kExecutionErrorInvalidArguments = 49, ///< Response: Process group does not exist - kExecutionErrorRequestFailed = 50, ///< Response: The process group is in a defined state - kExecutionErrorRequestSuccess = 51, ///< Response: Execution error reported for the given process group -}; - -/// @brief A message that can be a request, and acknowledgement or a response -struct ControlClientMessage final -{ - ControlClientID originating_control_client_; ///< ID of the individual Control Client and state manager process - ControlClientCode request_or_response_; ///< Request code (SM -> LM) or acknowledgement/response code (LM -> SM) - ProcessGroupStateID process_group_state_; ///< Payload for most requests & responses - uint32_t execution_error_code_; ///< Additional payload for `kExecutionErrorRequestSuccess` and - ///< `kFailedUnexpectedTermination` - // Constructor to initialize all data members - ControlClientMessage() - : originating_control_client_(), - request_or_response_(ControlClientCode::kNotSet), - process_group_state_(), - execution_error_code_(0) - { - } -}; - -/// @brief Represents a mapping between a ControlClientCode and its corresponding description string. -// RULECHECKER_comment(1, 1, check_incomplete_data_member_construction, "wi 45913 - This struct is POD, which doesn't -// have user-declared constructor. The rule doesn’t apply.", false) -struct ControlClientCodeMapping -{ - ControlClientCode code; - const char* description; -}; - -/// @brief Communications channel used for requests and responses -struct ControlClientComms final -{ - std::atomic_bool empty_; ///< true when a message can be placed, false when one may be read - ControlClientMessage msg_; ///< The message to be sent - // Constructor to initialize all data members - ControlClientComms() : empty_(true), msg_() - { - } -}; - -class ControlClientChannel; -using ControlClientChannelP = std::shared_ptr; - -/// @brief The bidirectional communications channel between a state manager and the Launch Manager -/// @note A Control Client message contains both the message from a Control Client to -/// Launch Manager and the response back from Launch Manager to the -/// Control Client. -/// Details about the originating Control Client and state manager are filled -/// in as they become available. The state manager is responsible for the -/// Control Client details; these are just fields Launch Manager will record -/// and send back with responses. Launch Manager fills in the details about -/// the state manager, i.e., which process of which process group originated -/// a request. -/// The Control Client handler is responsible for directing the request to the -/// correct graph (process group). The graph stores a copy of the last -/// requesting Control Client & state manager process. This information is -/// written by the Control Client handler. -/// A response to a transition request is routed to the correct process & state -/// client by copying the information stored in the request. -/// A report of an asynchronous event (an unexpected termination resulting in an -/// undefined state of the process group) will be routed to the correct process -/// and state by copying the information stored in the graph. -/// -class ControlClientChannel final -{ - public: - /// @brief Constructor, deleted. We cannot create or delete objects of this type in the normal ways. - ControlClientChannel() = delete; - - /// @brief Copy Constructor, deleted. We cannot create or delete objects of this type in the normal ways. - ControlClientChannel(const ControlClientChannel& other) = delete; - - /// @brief Move Constructor, deleted. We cannot create or delete objects of this type in the normal ways. - ControlClientChannel(ControlClientChannel&& other) = delete; - - /// @brief Copy assignment operator, deleted. We cannot create or delete objects of this type in the normal ways. - ControlClientChannel& operator=(const ControlClientChannel& other) = delete; - - /// @brief Move assignment operator, deleted. We cannot create or delete objects of this type in the normal ways. - ControlClientChannel& operator=(const ControlClientChannel&& other) = delete; - - /// @brief Desctructor, deleted. We cannot create or delete objects of this type in the normal ways. - ~ControlClientChannel() = delete; - - /// @brief Initialise the comms channels - /// called when the shared memory is initially created by Launch Manager - void initialize(); - - /// @brief Deinitialize the comms channels - /// called when the shared memory is destroyed by Launch Manager - void deinitialize(); - - /// @brief Send a request to the LM and get the response - /// Used by the Control Client to send a request. - /// Posts on the semaphore channel to wake Control Client handler up. - /// Will block until the request is acknowledged. - /// Not thread-safe; expected to be used by one thread only. - /// Note there is no time-out, because if LM is not - /// responding we are doomed anyway. - /// @param msg the message to send and response to receive - void sendRequest(ControlClientMessage& msg); - - /// @brief Poll for a request from the Control Client - /// This is used by Launch Manager to poll for requests from Control Clients - /// @return true if a request is available, false otherwise - bool getRequest(); - - /// @brief Acknowledge the request from Control Client - /// Used by Launch Manager to inform Control Client that - /// a message has been processed and the immediate response - /// is ready - void acknowledgeRequest(); - - /// @brief Return a reference to the current request - /// Used by Launch Manager to handle requests from state manager - /// @return Reference of ControlClientMessage - ControlClientMessage& request(); - - /// @brief Send a response to the Control Client. Not threadsafe. - /// @param msg the message to send. If there is no room the message is not sent - /// @return True on success, false if message not sent - bool sendResponse(ControlClientMessage& msg); - - /// @brief Get a response from the Launch Manager & acknowledge it - /// This is used by State Manager to read responses - /// @param msg Where to put (a copy of) the response message - /// @return true if a response was available, false otherwise - bool getResponse(ControlClientMessage& msg); - - /// @brief This static method returns a pointer to a ControlClientChannel object - /// The object won't exist unless the comms_type_ is kControlClient - /// Note that the ControlClientChannel object is in the shared memory after the Comms object - /// @param fd - the file descriptor to use, defaults to Comms::sync_fd - /// @return pointer to the corresponding ControlClientChannel, or nullptr if it does not exist - /// @note This method is for use by the Launch Manager and the Control Client, the Control Client always uses - /// the default parameter. - static ControlClientChannelP initializeControlClientChannel( - int fd = osal::IpcCommsSync::sync_fd, - osal::IpcCommsP* mem_ptr = nullptr); - - /// @brief This static method returns a pointer to a ControlClientChannel object - /// @param sync a Shared pointer to an existing Comms object - /// Note that the ControlClientChannel object is in the shared memory after the Comms object - /// Note also that we take a copy of the pointer to the base comms object. - /// @return pointer to the corresponding ControlClientChannel, or nullptr if it does not exist - /// @note this method is for use by the Launch Manager only! - static ControlClientChannelP getControlClientChannel(osal::IpcCommsP sync); - - /// @brief post on the global shared semaphore to notify the Control Client handler of an action - static void nudgeControlClientHandler(); - - /// @brief post on the shared sempahore for this state manager to nudge the LM handler thread - void nudgeLMHandler(); - - /// @brief Release ownership of the parent comms mapping stored inside the channel - /// @details The ControlClientChannel lives in shared memory and its destructor is never run, so any - /// std::shared_ptr members would leak their control blocks unless explicitly cleared. This call - /// drops the internal reference to the IpcCommsSync mapping allowing it to be unmapped when - /// external owners release theirs. - void releaseParentMapping(); - - /// @brief semaphore pointer for nudging the Control Client handler (for LM only) - static osal::Semaphore* nudgeControlClientHandler_; - - /// @brief Requests (SM -> LM) appear here, i.e. for communication started by SM - ControlClientComms request_; - - /// @brief Responses (LM -> SM) are put here, i.e. for communication started by LM - ControlClientComms response_; - - /// @brief Count of requests to obtain the initial state transition result - uint16_t initial_result_count_; - - /// @brief A utility function that converts codes to strings for logging purposes - /// @param code The code to convert - /// @return A string representing the code - std::string_view toString(ControlClientCode code); - - private: - /// @brief Ensure that the ControlClientChannel was setup properly before - /// accessing it - static bool is_initialized_; - static std::condition_variable init_cv_; - static std::mutex init_mutex_; - - /// @brief Copy of parent pointer when created from an `IpcCommsSync` object - /// @details When a `ControlClientChannel` is created by Launch Manager, we do so by using - /// the already mapped memory, since we've close the file descriptor at this point. We need to - /// take a copy of the pointer to make sure that the memory is not unmapped. - osal::IpcCommsP ipc_parent_; - - /// @brief Semaphore to nudge the LM handler thread in each state manager - osal::Semaphore nudge_LM_Handler_; -}; -/// @brief Define a constexpr array of ControlClientCodeMapping structures -/// Each element in the array maps a ControlClientCode to its corresponding description -// coverity[autosar_cpp14_m3_4_1_violation:INTENTIONAL] The value is used in a global context. -constexpr ControlClientCodeMapping stateArray[] = { - {ControlClientCode::kNotSet, "kNotSet"}, - {ControlClientCode::kInvalidRequest, "kInvalidRequest"}, - {ControlClientCode::kSetStateRequest, "kSetStateRequest"}, - {ControlClientCode::kSetStateInvalidArguments, "kSetStateInvalidArguments"}, - {ControlClientCode::kSetStateCancelled, "kSetStateCancelled"}, - {ControlClientCode::kSetStateFailed, "kSetStateFailed"}, - {ControlClientCode::kSetStateSuccess, "kSetStateSuccess"}, - {ControlClientCode::kSetStateAlreadyInState, "kSetStateAlreadyInState"}, - {ControlClientCode::kSetStateTransitionToSameState, "kSetStateTransitionToSameState"}, - {ControlClientCode::kFailedUnexpectedTerminationOnEnter, "kFailedUnexpectedTerminationOnEnter"}, - {ControlClientCode::kFailedUnexpectedTermination, "kFailedUnexpectedTermination"}, - {ControlClientCode::kGetInitialMachineStateRequest, "kGetInitialMachineStateRequest"}, - {ControlClientCode::kInitialMachineStateNotSet, "kInitialMachineStateNotSet"}, - {ControlClientCode::kInitialMachineStateFailed, "kInitialMachineStateFailed"}, - {ControlClientCode::kInitialMachineStateSuccess, "kInitialMachineStateSuccess"}, - {ControlClientCode::kGetExecutionErrorRequest, "kGetExecutionErrorRequest"}, - {ControlClientCode::kExecutionErrorInvalidArguments, "kExecutionErrorInvalidArguments"}, - {ControlClientCode::kExecutionErrorRequestFailed, "kExecutionErrorRequestFailed"}, - {ControlClientCode::kExecutionErrorRequestSuccess, "kExecutionErrorRequestSuccess"}, -}; - -} // namespace score::mw::lifecycle::internal - -#endif // CONTROL_CLIENT_CHANNEL_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/main.cpp b/score/launch_manager/src/daemon/src/main.cpp index 9ffa87a35d..49f382c95b 100644 --- a/score/launch_manager/src/daemon/src/main.cpp +++ b/score/launch_manager/src/daemon/src/main.cpp @@ -131,13 +131,11 @@ int main(int argc, const char* argv[]) return EXIT_FAILURE; } } - // reserve files descriptor osal::IpcCommsSync::sync_fd (fd3) and - // osal::IpcCommsSync::control_client_handler_nudge_fd (fd4) for communication tpyes: kNoComms !fd3 & !fd4 + // reserve files descriptor osal::IpcCommsSync::sync_fd (fd3) + // for communication tpyes: kNoComms !fd3 & !fd4 // kReporting fd3 & !fd4 - // kControlClient fd3 & fd4 // the file descriptors are closed inside the handleComms function. reserveFD(osal::IpcCommsSync::sync_fd); - reserveFD(osal::IpcCommsSync::control_client_handler_nudge_fd); int exit_code = EXIT_FAILURE; @@ -199,7 +197,6 @@ int main(int argc, const char* argv[]) } close(osal::IpcCommsSync::sync_fd); - close(osal::IpcCommsSync::control_client_handler_nudge_fd); LM_LOG_INFO() << "Launch Manager completed with exit code value:" << exit_code; diff --git a/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp b/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp index b7e3cc9c9a..206e9f0944 100644 --- a/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp +++ b/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp @@ -53,9 +53,7 @@ struct IpcCommsSync final /// @brief Type of communications used for this process. /// The `comms_type_` member identifies whether the process has no communications - /// with Launch Manager (`kNoComms`), is expected to report kRunning (`kReporting`) - /// or is a state manager (`kControlClient`) i.e., a process that is allowed to use - /// the Control Client interface. + /// with Launch Manager (`kNoComms`) or is expected to report kRunning (`kReporting`). CommsType comms_type_; /// @brief Constant for the synchronization file descriptor. @@ -63,11 +61,6 @@ struct IpcCommsSync final /// during communication. It is set to a value of 111 by default. static const int sync_fd = 111; - /// @brief Constant for the file descriptor used to signal state transitions - /// The semaphore used to signal state transitions is stored in an unlinked shared memory area. This requires - /// the constant “control_client_handler_nudge_fd” so that the spawned processes can access this resource. - static const int control_client_handler_nudge_fd = 4; - // Cannot construct or destruct objects of this type /// @brief Constructor (deleted) diff --git a/score/launch_manager/src/daemon/src/osal/return_types.hpp b/score/launch_manager/src/daemon/src/osal/return_types.hpp index f81a031e04..f16c5455be 100644 --- a/score/launch_manager/src/daemon/src/osal/return_types.hpp +++ b/score/launch_manager/src/daemon/src/osal/return_types.hpp @@ -27,16 +27,11 @@ namespace score::mw::lifecycle::internal::osal using ProcessID = pid_t; -/// @brief This enum class is used to distinguish between different types of communication required by processes -/// The information is initially reported by configuration manager in the startup_config_ member of the OsConfig -/// structure and is used by ProcessGroupManager to initially create the correct size of shared memory and also -/// by Control Client library to determine if a process is allowed to report kRunning and if it is allowed to use the -/// Control Client interfaces. +/// @brief The type of communications used for a child process. enum class CommsType : std::uint_least8_t { - kNoComms = 0, // Do not create any communications channel - kReporting = 1, // Create an osal::Comms object only - kControlClient = 2, // Create an osal::Comms object and reserve space for a ControlClientChannel + kNoComms = 0, // Do not create any communications channel + kReporting = 1, // Create an osal::Comms object only }; ///@brief This enum class likely represents the return status or outcome of an operating system abstraction layer (OSAL) 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 fba3d4a79b..2f28811a01 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/BUILD @@ -86,11 +86,9 @@ cc_library( "//score/launch_manager/src/daemon/src/common/concurrency:mpmc_concurrent_queue", "//score/launch_manager/src/daemon/src/common/concurrency:thread_pool", "//score/launch_manager/src/daemon/src/configuration:config", - "//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:wait_for_file", "//score/launch_manager/src/daemon/src/process_group_manager/details:graph", - "//score/launch_manager/src/daemon/src/process_group_manager/details:itransition_result_publisher", "//score/launch_manager/src/daemon/src/process_group_manager/details:os_handler", "//score/launch_manager/src/daemon/src/process_group_manager/details:process_info_node", "//score/launch_manager/src/daemon/src/process_group_manager/details:process_launcher", @@ -99,6 +97,7 @@ cc_library( "//score/launch_manager/src/daemon/src/recovery_client", "//score/launch_manager/src/daemon/src/supervision_control_client:isupervision_control_notifier", "//score/launch_manager/src/daemon/src/watchdog:i_watchdog_if", + "//score/launch_manager/src/lm_control", "@score_baselibs//score/language/futurecpp", ], ) 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 5161c140ef..b3f6b7f860 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 @@ -163,7 +163,6 @@ cc_library( ":safe_process_map", "//score/launch_manager/src/daemon/src/common:alive_interface_path", "//score/launch_manager/src/daemon/src/configuration:component_config", - "//score/launch_manager/src/daemon/src/control:control_client_channel", "//score/launch_manager/src/daemon/src/osal:ifile_waiter", "//score/launch_manager/src/daemon/src/osal:ipc_comms", "//score/launch_manager/src/daemon/src/osal:semaphore", @@ -212,18 +211,18 @@ cc_library( ":component_of", ":component_task", ":dependency_graph", - ":itransition_result_publisher", ":process_handling", ":process_info_node", ":run_target", ":safe_process_map", ":transition", "//score/launch_manager/src/daemon/src/common:identifier_hash", + "//score/launch_manager/src/daemon/src/common:process_group_state_id", "//score/launch_manager/src/daemon/src/common/concurrency:mpmc_concurrent_queue", "//score/launch_manager/src/daemon/src/configuration:config", - "//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", + "//score/launch_manager/src/lm_control", ], ) @@ -373,7 +372,6 @@ cc_library( deps = [ "//score/launch_manager/src/daemon/src/common:log", "//score/launch_manager/src/daemon/src/common:signal_safe_log", - "//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:security_policy", "//score/launch_manager/src/daemon/src/osal:set_affinity", @@ -416,14 +414,3 @@ lm_cc_test( "@googletest//:gtest_main", ], ) - -cc_library( - name = "itransition_result_publisher", - hdrs = ["itransition_result_publisher.hpp"], - 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 = [ - "//score/launch_manager/src/daemon/src/control:control_client_channel", - ], -) 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 2ae43239c4..764fc3e3e0 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 @@ -24,6 +24,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/lifecycle/run_target_activation_source.hpp" #include "score/assert.hpp" @@ -119,18 +120,15 @@ Graph::Graph( configuration::Config& configuration, std::shared_ptr job_queue, ProcessHandling process_handling, - ITransitionResultPublisher* transition_result_receiver) + LmControlSkeleton skeleton) : nodes_(max_num_nodes), transition_builder_(nodes_), state_(GraphState::kSuccess), configuration_(configuration), job_queue_(job_queue), process_handling_(std::move(process_handling)), - transition_result_receiver_(transition_result_receiver) + skeleton_(std::move(skeleton)) { - last_state_manager_.process_identifier_ = IdentifierHash{""}; // an invalid state manager - last_state_manager_.process_group_index_ = 0xFFFFU; - cancel_message_.request_or_response_ = ControlClientCode::kNotSet; CreateDependencyGraph(nodes_, configuration_, process_handling_, off_state_transition_timeout_); } @@ -221,10 +219,52 @@ void Graph::queueReadyNodes() void Graph::finalizeTransitionSuccess() { + auto allocate_result = skeleton_.activation_result.Allocate(); + if (allocate_result.has_value()) + { + ActivationResult* event = allocate_result.value().Get(); + + { + const std::lock_guard lock(IdentifierHash::get_registry_mutex()); + const auto& registry = IdentifierHash::get_registry(); + const auto it = registry.find(getProcessGroupState().data()); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + it != registry.end(), "IdentifierHash does not correspond to an existing name"); + event->activated_run_target = RunTargetName(it->second); + } + + if (is_initial_state_transition_) + { + event->activation_source = RunTargetActivationSource::kInitialActivation; + } + else if (getProcessGroupState() == IdentifierHash{"fallback"}) + { + event->activation_source = RunTargetActivationSource::kRecoveryAction; + } + else + { + event->activation_source = RunTargetActivationSource::kStateManagerRequest; + } + + const auto send_result = skeleton_.activation_result.Send(std::move(allocate_result.value())); + if (send_result.has_value()) + { + LM_LOG_DEBUG() << "Sent the activation result to the state manager"; + } + else + { + LM_LOG_ERROR() << "Failed to send the activation result to the state manager"; + } + } + else + { + LM_LOG_ERROR() << "Failed to allocate space to send the activation result to the state manager:" + "check that the mw::com configuration is correct"; + } + if (is_initial_state_transition_) { is_initial_state_transition_ = false; - transition_result_receiver_->setInitialStateTransitionResult(ControlClientCode::kInitialMachineStateSuccess); // RULECHECKER_comment(1, 3, check_c_style_cast, "This is the definition provided by the OS and does // a C-style cast.", true) @@ -233,8 +273,8 @@ void Graph::finalizeTransitionSuccess() // weird value in debug messages. << (static_cast(clock()) / (static_cast(CLOCKS_PER_SEC) / 1000.0)) << "ms"; } + setState(GraphState::kSuccess); - setPendingEvent(ControlClientCode::kSetStateSuccess); } void Graph::tryQueueNode(ComponentTask task) @@ -304,7 +344,6 @@ void Graph::startInitialTransition(IdentifierHash pg_state) if (!startTransition(pg_state)) { is_initial_state_transition_ = false; - transition_result_receiver_->setInitialStateTransitionResult(ControlClientCode::kInitialMachineStateFailed); } } @@ -401,7 +440,6 @@ void Graph::handleNonTransitionExecution(GraphState current_state) if (is_initial_state_transition_) { is_initial_state_transition_ = false; - transition_result_receiver_->setInitialStateTransitionResult(ControlClientCode::kInitialMachineStateFailed); // RULECHECKER_comment(1, 3, check_c_style_cast, "This is the definition provided by the OS and does a C-style // cast.", true) coverity[cert_err33_c_violation:INTENTIONAL] Does not matter if clock() gives a weird value in // debug messages. @@ -418,17 +456,9 @@ void Graph::handleNonTransitionExecution(GraphState current_state) } setState(GraphState::kUndefinedState); - if (current_state == GraphState::kAborting) - { - setPendingEvent(abort_code_); - } - else - { - ControlClientChannel::nudgeControlClientHandler(); - } } -void Graph::abort(uint32_t code, IComponent::ComponentError reason) +void Graph::abort(uint32_t code, [[maybe_unused]] IComponent::ComponentError reason) // TODO: Use reason { if (!setState(GraphState::kAborting)) { @@ -436,26 +466,11 @@ void Graph::abort(uint32_t code, IComponent::ComponentError reason) return; } last_execution_error_ = code; - switch (reason) - { - case IComponent::ComponentError::kErrorAfterReady: - abort_code_ = ControlClientCode::kFailedUnexpectedTermination; - break; - case IComponent::ComponentError::kErrorBeforeReady: - abort_code_ = ControlClientCode::kFailedUnexpectedTerminationOnEnter; - break; - default: - abort_code_ = ControlClientCode::kSetStateFailed; - break; - } } void Graph::cancel() { - if (setState(GraphState::kCancelled)) - { - setPendingEvent(ControlClientCode::kSetStateCancelled); - } + setState(GraphState::kCancelled); if (jobs_in_progress_ > 0) { @@ -481,24 +496,6 @@ void Graph::forceKillProcesses() } } -void Graph::updateCancelMessage() -{ - ControlClientCode code = getPendingEvent(); - - if (code != ControlClientCode::kNotSet) - { - cancel_message_.process_group_state_ = requested_state_; - cancel_message_.originating_control_client_ = last_state_manager_; - cancel_message_.request_or_response_ = code; - clearPendingEvent(code); - } -} - -void Graph::setStateManager(ControlClientID& control_client_id) -{ - last_state_manager_ = control_client_id; -} - ProcessInfoNode* Graph::getProcessInfoNode(IdentifierHash process_index) { if (nodes_.find(process_index) == nodes_.end()) @@ -525,30 +522,6 @@ IdentifierHash Graph::getProcessGroupState() return requested_state_.pg_state_name_; } -const ProcessInfoNode* Graph::findControlClient() -{ - auto* pin = getProcessInfoNode(getStateManager().process_identifier_); - if (pin && pin->getControlClientChannel()) - { - return pin; - } - - for (const auto [id, node] : nodes_) - { - if (const auto* process = std::get_if(&node); process && process->getControlClientChannel()) - { - return process; - } - } - - return nullptr; -} - -ControlClientID Graph::getStateManager() -{ - return last_state_manager_; -} - uint32_t Graph::getLastExecutionError() { return last_execution_error_; @@ -578,30 +551,6 @@ IdentifierHash Graph::getPendingState() return pending_state_; } -ControlClientCode Graph::getPendingEvent() -{ - return event_; -} - -void Graph::clearPendingEvent(ControlClientCode expected) -{ - if (event_ == expected) - { - event_ = ControlClientCode::kNotSet; - } -} - -void Graph::setPendingEvent(ControlClientCode event) -{ - event_ = event; - ControlClientChannel::nudgeControlClientHandler(); -} - -ControlClientMessage& Graph::getCancelMessage() -{ - return cancel_message_; -} - std::string_view Graph::toString(GraphState state) { switch (state) 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 8abb965a11..c2f6a31cb0 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 @@ -26,20 +26,20 @@ #include "score/mw/launch_manager/common/concurrency/mpmc_concurrent_queue.hpp" #include "score/mw/launch_manager/common/identifier_hash.hpp" +#include "score/mw/launch_manager/common/process_group_state_id.hpp" #include "score/mw/launch_manager/configuration/config.hpp" -#include "score/mw/launch_manager/control/control_client_channel.hpp" #include "score/mw/launch_manager/osal/semaphore.hpp" #include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" #include "score/mw/launch_manager/process_group_manager/details/component_of.hpp" #include "score/mw/launch_manager/process_group_manager/details/component_task.hpp" #include "score/mw/launch_manager/process_group_manager/details/dependency_graph.hpp" -#include "score/mw/launch_manager/process_group_manager/details/itransition_result_publisher.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_handling.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_info_node.hpp" #include "score/mw/launch_manager/process_group_manager/details/run_target.hpp" #include "score/mw/launch_manager/process_group_manager/details/transition.hpp" #include "score/mw/launch_manager/process_group_manager/iprocess.hpp" #include "score/mw/launch_manager/supervision_control_client/isupervision_event_publisher.hpp" +#include "score/mw/lifecycle/details/lm_control_service.h" #include namespace score::mw::lifecycle::internal @@ -155,7 +155,7 @@ class Graph final configuration::Config& configuration, std::shared_ptr job_queue, ProcessHandling process_handling, - ITransitionResultPublisher* transition_result_receiver); + LmControlSkeleton skeleton); /// @brief Destructor to clean up resources used by the Graph object. ~Graph(); @@ -227,19 +227,9 @@ class Graph final /// getState() returns GraphState::kSuccess. IdentifierHash getProcessGroupState(); - /// @return The ProcessInfoNode that has a ControlClientChannel, or nullptr if none exists. - const ProcessInfoNode* findControlClient(); - - /// @brief Sets the control client that is managing state transitions for this process group. - /// @param control_client_id The identifier of the new state manager. - void setStateManager(ControlClientID& control_client_id); - /// @brief Update the details for the cancel message to match the current state. void updateCancelMessage(); - /// @return Information about the control client managing this process group's state. - ControlClientID getStateManager(); - /// @return The error code set by the last process that caused an unexpected termination. uint32_t getLastExecutionError(); @@ -255,20 +245,6 @@ class Graph final /// @return The pending state, or an empty hash if no state is pending. IdentifierHash getPendingState(); - /// @return The pending event code, or kNotSet if there is none. - ControlClientCode getPendingEvent(); - - /// @brief Clears the pending event, but only if its current value matches expected. - /// @param expected The event code to compare against. - void clearPendingEvent(ControlClientCode expected); - - /// @brief Stores a pending event code and notifies the ProcessGroupManager to process it. - /// @param event The event code to store. - void setPendingEvent(ControlClientCode event); - - /// @return The cancel message prepared when updateCancelMessage() was called. - ControlClientMessage& getCancelMessage(); - /// @brief A utility function that converts codes to strings for logging purposes /// @param state The state to convert /// @return A string representing the state @@ -359,12 +335,6 @@ class Graph final /// @brief The interfaces passed to the process nodes to control their OS processes ProcessHandling process_handling_; - /// @brief Class to receive information about the initial state transition result - ITransitionResultPublisher* transition_result_receiver_; - - /// @brief The state manager node for this process group - ControlClientID last_state_manager_{}; - /// @brief The last execution error set on an unexpected termination uint32_t last_execution_error_{0U}; @@ -374,15 +344,6 @@ class Graph final /// @brief The pending state transition, if any IdentifierHash pending_state_{""}; - /// @brief Any pending event to report - ControlClientCode event_{ControlClientCode::kNotSet}; - - /// @brief Reason that tha graph was aborted - ControlClientCode abort_code_{ControlClientCode::kNotSet}; - - /// @brief The message to send when a transition is cancelled - ControlClientMessage cancel_message_{}; - /// @brief Constant for Off state. const IdentifierHash off_state_{"Off"}; @@ -394,6 +355,9 @@ class Graph final /// @brief Transition timeout for Off state std::chrono::milliseconds off_state_transition_timeout_{0}; + + // TODO: Move to interface + LmControlSkeleton skeleton_; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/graph_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/graph_UT.cpp index f1ddd08b12..dcd7102dc3 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/graph_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/graph_UT.cpp @@ -38,12 +38,6 @@ class MockProcessMap : public SafeProcessMapInserter MOCK_METHOD(SafeProcessMapReturnType, insertIfNotTerminated, (osal::ProcessID key, IComponent* object), (override)); }; -class MockTransitionResultPublisher : public ITransitionResultPublisher -{ - public: - MOCK_METHOD(void, setInitialStateTransitionResult, (ControlClientCode result), (override)); -}; - class GraphTest : public ::testing::Test { protected: @@ -63,8 +57,7 @@ class GraphTest : public ::testing::Test 10U, config_.value(), job_queue_, - ProcessHandling{mock_supervision_event_publisher_, &process_interface_, mock_process_map}, - &mock_transition_result_publisher_); + ProcessHandling{mock_supervision_event_publisher_, &process_interface_, mock_process_map}); } virtual void SetConfig() @@ -184,7 +177,6 @@ class GraphTest : public ::testing::Test StrictMock process_interface_{}; std::shared_ptr mock_process_map = std::make_shared(); NiceMock mock_supervision_event_publisher_{}; - MockTransitionResultPublisher mock_transition_result_publisher_{}; std::unique_ptr graph_{}; static constexpr std::string_view pg_string{"MainPG"}; @@ -274,10 +266,6 @@ TEST_F(GraphInitialTransitionTest, nothingToDo) { RecordProperty("Description", "Test that the initial transition to an empty run target succeeds immediately"); - EXPECT_CALL( - mock_transition_result_publisher_, - setInitialStateTransitionResult(ControlClientCode::kInitialMachineStateSuccess)); - graph_->startInitialTransition(IdentifierHash{startup.name}); EXPECT_EQ(graph_->getState(), GraphState::kSuccess); @@ -287,10 +275,6 @@ TEST_F(GraphInitialTransitionTest, jobFailure) { RecordProperty("Description", "Test that startInitialTransition() sends the correct result due to a failing job"); - EXPECT_CALL( - mock_transition_result_publisher_, - setInitialStateTransitionResult(ControlClientCode::kInitialMachineStateFailed)); - graph_->startInitialTransition(IdentifierHash{run_target_name(0)}); const auto job = job_queue_->pop()->value(); @@ -306,10 +290,6 @@ TEST_F(GraphInitialTransitionTest, cancel) RecordProperty( "Description", "Test that startInitialTransition() sends the correct result when the transition is cancelled"); - EXPECT_CALL( - mock_transition_result_publisher_, - setInitialStateTransitionResult(ControlClientCode::kInitialMachineStateFailed)); - graph_->startInitialTransition(IdentifierHash{run_target_name(0)}); graph_->cancel(); @@ -321,21 +301,6 @@ TEST_F(GraphInitialTransitionTest, cancel) EXPECT_EQ(graph_->getState(), GraphState::kUndefinedState); } -TEST_F(GraphInitialTransitionTest, unrecognizedRunTarget) -{ - RecordProperty( - "Description", - "Regression test for #542: startInitialTransition() with a run target name that doesn't exist in " - "the graph's configuration must still report kInitialMachineStateFailed, instead of leaving the " - "initial transition result unreported."); - - EXPECT_CALL( - mock_transition_result_publisher_, - setInitialStateTransitionResult(ControlClientCode::kInitialMachineStateFailed)); - - graph_->startInitialTransition(IdentifierHash{"NotARealRunTarget"}); -} - class GraphOffTransitionTest : public GraphTest { }; @@ -523,7 +488,6 @@ TEST_F(GraphHandleComponentEventTest, failureFollowedBySuccessFails) graph_->handleComponentEvent(ActivationSuccessful{second_job->value().component.get().getIdentifier()}); EXPECT_EQ(graph_->getState(), GraphState::kUndefinedState); - EXPECT_EQ(graph_->getPendingEvent(), ControlClientCode::kFailedUnexpectedTerminationOnEnter); } TEST_F(GraphHandleComponentEventTest, unexpectedTerminationDuringSuccess) @@ -576,8 +540,6 @@ TEST_F(GraphHandleComponentEventTest, unexpectedTerminationDuringTransition) const auto second_job = job_queue_->pop(); executeJobSuccessfully(second_job->value()); graph_->handleComponentEvent(ActivationSuccessful{second_job->value().component.get().getIdentifier()}); - - EXPECT_EQ(graph_->getPendingEvent(), ControlClientCode::kFailedUnexpectedTermination); } class GraphCancelTest : public GraphTest @@ -607,7 +569,6 @@ TEST_F(GraphCancelTest, cancelsOngoingTransition) graph_->handleComponentEvent(JobSkipped{IdentifierHash{process_name(0)}}); EXPECT_TRUE(job->value().stop_token.stop_requested()); - EXPECT_EQ(graph_->getPendingEvent(), ControlClientCode::kSetStateCancelled); EXPECT_EQ(graph_->getState(), GraphState::kUndefinedState); } @@ -702,31 +663,11 @@ TEST_F(GraphUtilitiesTest, gettersSetters) { RecordProperty("Description", "Test that basic getters return the value the setter sets"); - ControlClientID state_manager = {}; - state_manager.process_identifier_ = IdentifierHash{"123"}; - graph_->setStateManager(state_manager); - EXPECT_EQ(graph_->getStateManager().process_identifier_, state_manager.process_identifier_); - const IdentifierHash pending_state{"Pending"}; const auto previous_pending_state = graph_->getPendingState(); EXPECT_EQ(graph_->setPendingState(pending_state), previous_pending_state); EXPECT_EQ(graph_->getPendingState(), pending_state); - const ControlClientCode pending_event = ControlClientCode::kSetStateAlreadyInState; - graph_->setPendingEvent(pending_event); - EXPECT_EQ(graph_->getPendingEvent(), pending_event); - graph_->clearPendingEvent(ControlClientCode::kFailedUnexpectedTermination); - // Does not clear because expected doesn't match - EXPECT_EQ(graph_->getPendingEvent(), pending_event); - graph_->clearPendingEvent(pending_event); - // Now cleared - EXPECT_EQ(graph_->getPendingEvent(), ControlClientCode::kNotSet); - - const ControlClientCode cancel_event = ControlClientCode::kSetStateCancelled; - graph_->setPendingEvent(cancel_event); - graph_->updateCancelMessage(); - EXPECT_EQ(graph_->getCancelMessage().request_or_response_, cancel_event); - const auto before_time = std::chrono::steady_clock::now(); graph_->setRequestStartTime(); const auto after_time = std::chrono::steady_clock::now(); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/itransition_result_publisher.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/itransition_result_publisher.hpp deleted file mode 100644 index d0a12f36ce..0000000000 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/itransition_result_publisher.hpp +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************** - * 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 SCORE_LCM_ITRANSITION_RESULT_PUBLISHER -#define SCORE_LCM_ITRANSITION_RESULT_PUBLISHER - -#include "score/mw/launch_manager/control/control_client_channel.hpp" - -namespace score::mw::lifecycle::internal -{ - -class ITransitionResultPublisher -{ - public: - virtual void setInitialStateTransitionResult(ControlClientCode result) = 0; - - virtual ~ITransitionResultPublisher() = default; -}; - -} // namespace score::mw::lifecycle::internal - -#endif // SCORE_LCM_ITRANSITION_RESULT_PUBLISHER 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 ab943feecf..19b5639a98 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 @@ -195,12 +195,6 @@ IComponent::RequestResult ProcessInfoNode::tryHandleTermination(int32_t process_ } } - if (control_client_channel_) - { - control_client_channel_->releaseParentMapping(); - std::atomic_store(&control_client_channel_, ControlClientChannelP{}); - } - return res; } @@ -239,11 +233,6 @@ IComponent::RequestResult ProcessInfoNode::startProcess(score::cpp::stop_token s LM_LOG_DEBUG() << "startProcess pid" << pid_ << "received for process:" << identifier_ << "( startup time:" << std::chrono::round(launched_time - initial_time) << ")"; - if (configuration::ApplicationType::StateManager == - config_.component_properties.application_profile.application_type) - { - setupControlClientChannel(); - } auto res = handleProcessStarted(stop_token); if (!res.has_value()) { @@ -284,12 +273,6 @@ IComponent::RequestResult ProcessInfoNode::startProcess(score::cpp::stop_token s return tryReportCompletion(ProcessState::kRunning); } -void ProcessInfoNode::setupControlClientChannel() -{ - // Make sure we store the control_client_channel before waiting for kRunning - std::atomic_store(&control_client_channel_, ControlClientChannel::getControlClientChannel(sync_)); -} - score::cpp::expected_blank ProcessInfoNode::handleProcessStillStarting( const score::cpp::stop_token& stop_token) { @@ -503,9 +486,4 @@ IdentifierHash ProcessInfoNode::getIdentifier() const return identifier_; } -ControlClientChannelP ProcessInfoNode::getControlClientChannel() const -{ - return std::atomic_load(&control_client_channel_); -} - } // namespace score::mw::lifecycle::internal 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 b059ec2466..327a6249dd 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 @@ -16,7 +16,6 @@ #include "score/launch_manager/src/daemon/src/configuration/component_config.hpp" #include "score/mw/launch_manager/configuration/component_config.hpp" -#include "score/mw/launch_manager/control/control_client_channel.hpp" #include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_handling.hpp" #include "score/mw/launch_manager/process_group_manager/details/safe_process_map.hpp" @@ -57,7 +56,6 @@ class ProcessInfoNode final : public IComponent process_state_(other.process_state_.load()), reached_ready_(other.reached_ready_.load()), config_(std::move(other.config_)), - control_client_channel_(std::move(other.control_client_channel_)), sync_(std::move(other.sync_)), process_handling_(std::move(other.process_handling_)), identifier_(other.identifier_) @@ -88,9 +86,6 @@ class ProcessInfoNode final : public IComponent /// @return The configured shutdown_timeout for this process, or zero std::chrono::milliseconds getTerminationTimeout() const; - /// @return The ControlClientChannel for this process, or nullptr if none exists. - [[nodiscard]] ControlClientChannelP getControlClientChannel() const; - private: /// @brief Atomically transitions to new_state if the transition is valid. For reporting /// processes, also notifies the platform health manager of the state change. @@ -150,9 +145,6 @@ class ProcessInfoNode final : public IComponent /// @brief Sends SIGKILL repeatedly until the process exits or the stop token is triggered. void handleForcedTermination(const score::cpp::stop_token& stop_token); - /// @brief Creates the ControlClientChannel from the process's IPC comms handle. - void setupControlClientChannel(); - /// @brief semaphore used to check termination with timeout osal::Semaphore terminator_{}; @@ -175,9 +167,6 @@ class ProcessInfoNode final : public IComponent /// @brief Pointer to config for this process configuration::ComponentConfig config_; - /// @brief Pointer to the ControlClientChannel object if it exists - ControlClientChannelP control_client_channel_{nullptr}; - /// @brief Pointer to the comms for this process osal::IpcCommsP sync_{nullptr}; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node_UT.cpp index 0e38a596a3..5769382a42 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node_UT.cpp @@ -168,7 +168,6 @@ TEST_F(ProcessInfoNodeStartupTest, CanConstructIdleProcessInfoNode) ASSERT_THAT(node->getState(), Eq(score::mw::lifecycle::ProcessState::kIdle)); ASSERT_THAT(node->getPid(), Eq(0)); ASSERT_THAT(node->active(), IsFalse()); - ASSERT_THAT(node->getControlClientChannel(), IsNull()); } TEST_F(ProcessInfoNodeStartupTest, CanStartNonReportingProcess) @@ -185,7 +184,6 @@ TEST_F(ProcessInfoNodeStartupTest, CanStartNonReportingProcess) ASSERT_THAT(result.has_value(), IsTrue()); ASSERT_THAT(result.value(), Eq(IComponent::RequestState::kSuccess)); - ASSERT_THAT(node->getControlClientChannel(), IsNull()); ASSERT_THAT(node->getState(), Eq(score::mw::lifecycle::ProcessState::kRunning)); } @@ -202,7 +200,6 @@ TEST_F(ProcessInfoNodeStartupTest, CanStartReportingProcess_ReportsRunningInTime ASSERT_THAT(result.has_value(), IsTrue()); ASSERT_THAT(result.value(), Eq(IComponent::RequestState::kSuccess)); - ASSERT_THAT(node->getControlClientChannel(), IsNull()); ASSERT_THAT(node->getState(), Eq(score::mw::lifecycle::ProcessState::kRunning)); } @@ -574,7 +571,6 @@ TEST_F(ProcessInfoNodeMoveTest, MoveConstruct_IdleNode_PreservesObservableState) ASSERT_THAT(moved.getState(), Eq(score::mw::lifecycle::ProcessState::kIdle)); ASSERT_THAT(moved.active(), IsFalse()); ASSERT_THAT(moved.getPid(), Eq(0)); - ASSERT_THAT(moved.getControlClientChannel(), IsNull()); } TEST_F(ProcessInfoNodeMoveTest, MoveConstruct_RunningNode_PreservesAtomicState) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp index 0722962c6a..97956b0c79 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp @@ -24,7 +24,6 @@ #include "score/mw/launch_manager/common/log.hpp" #include "score/mw/launch_manager/common/signal_safe_log.hpp" -#include "score/mw/launch_manager/control/control_client_channel.hpp" #include "score/mw/launch_manager/osal/ipc_comms.hpp" #include "score/mw/launch_manager/osal/security_policy.hpp" #include "score/mw/launch_manager/osal/set_affinity.hpp" @@ -88,7 +87,6 @@ void handleComms(score::mw::lifecycle::internal::osal::ChildProcessConfig& param { // kNoComms !fd3 & !fd4 // kReporting fd3 & !fd4 - // kControlClient fd3 & fd4 if (!param.shared_block) { // kNoComms, fds are CLOEXEC @@ -98,7 +96,7 @@ void handleComms(score::mw::lifecycle::internal::osal::ChildProcessConfig& param param.fd = dup2(param.fd, param.shared_block->sync_fd); // always make sure we are using fd=3 param.shared_block->pid_ = getpid(); // Store pid for check at client end - // It must be ensured that sync_fd (f3) and control_client_handler_nudge_fd (fd4) remain open depending on + // It must be ensured that sync_fd (f3) remains open depending on // the communication type. Flag FD_CLOEXEC is cleared conditionally to ensure that the // respective file descriptor remains open after the execve call. switch (param.shared_block->comms_type_) @@ -112,19 +110,6 @@ void handleComms(score::mw::lifecycle::internal::osal::ChildProcessConfig& param static_cast(signal_safe_log_errno(errno, "fcntl at line ", __LINE__, " failed")); sysexit(EXIT_FAILURE); } - close(IpcCommsSync::control_client_handler_nudge_fd); - break; - case CommsType::kControlClient: - if (-1 == fcntl(IpcCommsSync::sync_fd, F_SETFD, 0)) - { - static_cast(signal_safe_log_errno(errno, "fcntl at line ", __LINE__, " failed")); - sysexit(EXIT_FAILURE); - } - if (-1 == fcntl(IpcCommsSync::control_client_handler_nudge_fd, F_SETFD, 0)) - { - static_cast(signal_safe_log_errno(errno, "fcntl at line ", __LINE__, " failed")); - sysexit(EXIT_FAILURE); - } break; default: static_cast(signal_safe_log( @@ -271,10 +256,6 @@ bool ProcessLauncher::setupComms(IpcCommsP& block, int& fd, const configuration: const auto app_type = config.component_properties.application_profile.application_type; size_t length = sizeof(IpcCommsSync); - if (configuration::ApplicationType::StateManager == app_type) - { - length += sizeof(ControlClientChannel); - } constexpr std::string_view kShmNamePrefix{"/ipc_shared_mem"}; std::array(ProcessLimits::maxLocalBuffSize)> shm_name{}; @@ -313,8 +294,7 @@ bool ProcessLauncher::setupComms(IpcCommsP& block, int& fd, const configuration: return false; } - block = (configuration::ApplicationType::StateManager == app_type) ? initializeControlClient(fd, config) - : IpcCommsSync::getCommsObject(fd); + block = IpcCommsSync::getCommsObject(fd); if (!block) { return false; @@ -323,9 +303,6 @@ bool ProcessLauncher::setupComms(IpcCommsP& block, int& fd, const configuration: // Map application type to CommsType for backward compatibility switch (app_type) { - case configuration::ApplicationType::StateManager: - block->comms_type_ = CommsType::kControlClient; - break; case configuration::ApplicationType::Native: block->comms_type_ = CommsType::kNoComms; break; @@ -346,22 +323,6 @@ bool ProcessLauncher::setupComms(IpcCommsP& block, int& fd, const configuration: return true; } -IpcCommsP ProcessLauncher::initializeControlClient(int& fd, const configuration::ComponentConfig& config) -{ - LM_LOG_DEBUG() << "Initialize the control client for" << config.name << " process"; - /* Initialise the control client communications */ - IpcCommsP shared_block = nullptr; - ControlClientChannelP scc = ControlClientChannel::initializeControlClientChannel(fd, &shared_block); - if (!scc) - { - LM_LOG_ERROR() << "Failed to obtain ControlClientChannel for " << config.name - << ": initializeControlClientChannel returned nullptr"; - return nullptr; // Caller will see shared_block maybe null and treat as failure later. - } - scc->initialize(); - return shared_block; -} - bool ProcessLauncher::initializeSemaphores(IpcCommsP shared_block) { bool result = true; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp index 175462f707..2756ecf2ed 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp @@ -61,14 +61,6 @@ class ProcessLauncher final : public IProcess /// @return True if semaphore initialization is successful, false otherwise. bool initializeSemaphores(IpcCommsP block); - /// @brief Initializes the Control Client for communication using the shared memory block. - /// @param[in,out] fd Reference to store the file descriptor of the shared memory. - /// @param[in] config Pointer to the configuration for initializing the Control Client. - /// @return None. - IpcCommsP initializeControlClient( - int& fd, - const score::mw::lifecycle::internal::configuration::ComponentConfig& config); - /// @brief Handles the execution of the child process after forking. /// @param[in] param Reference to child process configuration. void handleChildProcess(ChildProcessConfig& param); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/process_group_manager.cpp b/score/launch_manager/src/daemon/src/process_group_manager/process_group_manager.cpp index 0c478c0745..d5a546d486 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/process_group_manager.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/process_group_manager.cpp @@ -17,10 +17,12 @@ #include #include +#include "score/mw/com/types.h" #include "score/mw/launch_manager/common/log.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_monitor.hpp" #include "score/mw/launch_manager/process_group_manager/ialive_monitor_thread.hpp" #include "score/mw/launch_manager/process_group_manager/process_group_manager.hpp" +#include "score/mw/lifecycle/details/lm_control_service.h" namespace score::mw::lifecycle::internal { @@ -77,11 +79,6 @@ bool ProcessGroupManager::initialize() sigaction(SIGUSR2, &action, NULL); sigaction(SIGVTALRM, &action, NULL); - if (!initializeControlClientHandler()) - { - return false; - } - const std::size_t total_processes = configuration_.components().size(); if (total_processes > static_cast(ProcessLimits::kMaxProcesses)) @@ -146,64 +143,6 @@ void ProcessGroupManager::deinitialize() process_monitor_.reset(); } -bool ProcessGroupManager::initializeControlClientHandler() -{ - bool result = false; - - // Create shared memory for the nudge semaphore, using the specific - // file descriptor osal::Comms::control_client_handler_nudge_fd, and a random name. - // The name is removed from the file system after creation, memory - // is mapped and a pointer stored, the FD is kept open. - ControlClientChannel::nudgeControlClientHandler_ = nullptr; - char shm_name[static_cast(score::mw::lifecycle::internal::ProcessLimits::maxLocalBuffSize)]; - - static_cast(snprintf( - shm_name, - static_cast(score::mw::lifecycle::internal::ProcessLimits::maxLocalBuffSize), - "/_nudge~._.~me_")); // random name - int fd = shm_open(shm_name, O_CREAT | O_EXCL | O_RDWR, 0U); - - if (fd >= 0) - { - shm_unlink(shm_name); - - if (0 == ftruncate(fd, static_cast(sizeof(osal::Semaphore)))) - { - int fd2 = - dup2(fd, osal::IpcCommsSync::control_client_handler_nudge_fd); // always make sure we are using fd=4 - close(fd); - - // dup2 clears the O_CLOEXEC flag so this needs to be set again - if (fcntl(fd2, F_SETFD, FD_CLOEXEC) != 0) - { - ::close(fd2); - return false; - } - - if (osal::IpcCommsSync::control_client_handler_nudge_fd == fd2) - { - void* buf = mmap(NULL, sizeof(osal::Semaphore), PROT_READ | PROT_WRITE, MAP_SHARED, fd2, 0); - - // RULECHECKER_comment(1, 1, check_c_style_cast, "This is the definition provided by the OS and does a - // C-style cast.", true) - if (MAP_FAILED != buf) - { - ControlClientChannel::nudgeControlClientHandler_ = static_cast(buf); - // coverity[cert_mem52_cpp_violation:FALSE] The allocated memory is checked by the containing if - // statement. - const auto osal_result = ControlClientChannel::nudgeControlClientHandler_->init(0U, true); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - osal_result == osal::OsalReturnType::kSuccess, "ControlClientChannel semaphore init failed"); - - result = true; - } - } - } - } - - return result; -} - bool ProcessGroupManager::initializeProcessGroups() { graph_ = std::make_shared( @@ -212,7 +151,7 @@ bool ProcessGroupManager::initializeProcessGroups() configuration_, worker_jobs_, ProcessHandling{*supervision_control_notifier_.get(), &process_interface_, process_map_, &file_waiter_}, - this); + this->offerService()); LM_LOG_DEBUG() << "Process group initialized successfully"; return true; @@ -247,6 +186,75 @@ void ProcessGroupManager::createProcessComponentsObjects(std::size_t total_proce worker_jobs_, static_cast(ProcessLimits::kNumWorkerThreads), *process_monitor_); } +[[nodiscard]] LmControlSkeleton ProcessGroupManager::offerService() +{ + const auto instance_specifier = + score::mw::com::InstanceSpecifier::Create(std::string{"LaunchManager/StateManager/Instance"}); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + instance_specifier.has_value(), instance_specifier.error().Message().data()); + + auto instance_result = LmControlSkeleton::Create(instance_specifier.value()); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(instance_result.has_value(), instance_result.error().Message().data()); + auto instance = std::move(instance_result).value(); + + const auto activate_run_target_register_result = instance.activate_run_target.RegisterHandler( + [this](ActivateRunTargetResponse& response, const ActivateRunTargetRequest& request) { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + request.mode == ActivationMode::kForced, "Only ActivationMode::kForced is implemented"); + + IdentifierHash new_state{request.run_target_name.data()}; + + if (!graph_->isValidRunTarget(new_state)) + { + response = ActivateRunTargetResponse{ + status : RequestStatus::kRejected, + rejection_reason : ExecErrc::kRunTargetDoesntExist + }; + return; + } + + if (graph_->getProcessGroupState() == new_state) + { + response = ActivateRunTargetResponse{ + status : RequestStatus::kRejected, + rejection_reason : ExecErrc::kAlreadyInState + }; + return; + } + + graph_->startTransition(new_state); + + response = ActivateRunTargetResponse{status : RequestStatus::kAccepted}; + }); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + activate_run_target_register_result.has_value(), activate_run_target_register_result.error().Message().data()); + + const auto get_active_run_target_register_result = + instance.get_active_run_target.RegisterHandler([this]([[maybe_unused]] GetActiveRunTargetResponse& response) { + if (graph_->getState() == GraphState::kInTransition) + { + response = + GetActiveRunTargetResponse{status : QueryStatus::kNotAvailable, run_target : RunTargetName("")}; + } + else + { + const IdentifierHash state = graph_->getProcessGroupState(); + const std::lock_guard lock(IdentifierHash::get_registry_mutex()); + const std::string& name = IdentifierHash::get_registry()[state.data()]; + + response = + GetActiveRunTargetResponse{status : QueryStatus::kAvailable, run_target : RunTargetName(name)}; + } + }); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + activate_run_target_register_result.has_value(), activate_run_target_register_result.error().Message().data()); + + const auto offer_result = instance.OfferService(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(offer_result.has_value(), offer_result.error().Message().data()); + + return instance; +} + bool ProcessGroupManager::run() { // RULECHECKER_comment(1, 4, check_c_style_cast, "This is the definition provided by the OS and does a C-style @@ -284,7 +292,6 @@ bool ProcessGroupManager::run() if (graph_) { - controlClientHandler(*graph_); processGroupHandler(*graph_); } @@ -394,138 +401,6 @@ void ProcessGroupManager::allProcessGroupsOff() } } -void ProcessGroupManager::controlClientHandler(Graph& pg) -{ - controlClientRequests(pg); - controlClientResponses(pg); -} - -void ProcessGroupManager::controlClientResponses(Graph& pg) -{ - // Are there any events to report to Control Clients for this process group? - ControlClientMessage msg; - - msg.request_or_response_ = pg.getPendingEvent(); - - if (ControlClientCode::kNotSet != msg.request_or_response_) - { - msg.process_group_state_.pg_name_ = pg.getProcessGroupName(); - msg.process_group_state_.pg_state_name_ = pg.getProcessGroupState(); - msg.originating_control_client_ = pg.getStateManager(); - msg.execution_error_code_ = pg.getLastExecutionError(); - - // Notice we leave two entries free in the message Q to allow for immediate - // responses, otherwise messages are left pending in the process group. - if (sendResponse(msg)) - { - pg.clearPendingEvent(msg.request_or_response_); - } - } - ControlClientMessage& cancel_msg = pg.getCancelMessage(); - - if (ControlClientCode::kNotSet != cancel_msg.request_or_response_) - { - if (sendResponse(cancel_msg)) - { - cancel_msg.request_or_response_ = ControlClientCode::kNotSet; - } - } -} - -bool ProcessGroupManager::sendResponse(ControlClientMessage msg) -{ - auto pin = getProcessInfoNode( - msg.originating_control_client_.process_group_index_, msg.originating_control_client_.process_identifier_); - bool ret = true; - - if (pin) - { - auto scc = pin->getControlClientChannel(); - - if (scc) - { - LM_LOG_DEBUG() << "ProcessGroupManager::ControlClientHandler: Sending" - << scc->toString(msg.request_or_response_) << "(" - << static_cast(msg.request_or_response_) << ") re state" - << msg.process_group_state_.pg_state_name_ << "of PG" << msg.process_group_state_.pg_name_; - ret = scc->sendResponse(msg); - if (!ret) - { - ControlClientChannel::nudgeControlClientHandler(); - } - } - } - - return ret; -} - -void ProcessGroupManager::controlClientRequests(Graph& pg) -{ - const auto* control_client = pg.findControlClient(); - - if (!control_client) - { - return; - } - - ControlClientChannelP scc = control_client->getControlClientChannel(); - - if (!scc) - { - return; - } - - if (scc->getRequest()) - { - // Fill in some routing details - // Single process group at index 0 - scc->request().originating_control_client_.process_group_index_ = 0U; - scc->request().originating_control_client_.process_identifier_ = control_client->getIdentifier(); - - LM_LOG_DEBUG() << "ProcessGroupManager::ControlClientHandler: got request" - << scc->toString(scc->request().request_or_response_) << "(" - << static_cast(scc->request().request_or_response_) << ") re state" - << scc->request().process_group_state_.pg_state_name_; - - // Now process the request - switch (scc->request().request_or_response_) - { - case ControlClientCode::kSetStateRequest: - processStateTransition(scc); - break; - - case ControlClientCode::kGetExecutionErrorRequest: - processGetExecutionError(scc); - break; - - case ControlClientCode::kGetInitialMachineStateRequest: - processGetInitialMachineStateTransitionResult(scc); - break; - - default: // Error, this is not a recognised request! - scc->request().request_or_response_ = ControlClientCode::kInvalidRequest; - break; - } - scc->acknowledgeRequest(); - } - - // now process deferred requests for initial state transition results - if (ControlClientCode::kInitialMachineStateNotSet != initial_state_transition_result_ && scc->initial_result_count_) - { - ControlClientMessage msg; - msg.request_or_response_ = initial_state_transition_result_; - msg.originating_control_client_ = scc->request().originating_control_client_; - if (scc->sendResponse(msg)) - { - scc->initial_result_count_--; - } - else - { - ControlClientChannel::nudgeControlClientHandler(); // will need to try again - } - } -} - void ProcessGroupManager::handleRecoveryRequest(const IdentifierHash& process_identifier) { SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(bool(graph_), "Graph not initialized"); @@ -546,7 +421,6 @@ void ProcessGroupManager::handleRecoveryRequest(const IdentifierHash& process_id (void)graph_->setPendingState(recovery_state_); graph_->setRequestStartTime(); graph_->cancel(); - controlClientResponses(*graph_); } else { @@ -567,89 +441,6 @@ void ProcessGroupManager::handleRecoveryRequest(const IdentifierHash& process_id } } -void ProcessGroupManager::processStateTransition(ControlClientChannelP scc) -{ - - IdentifierHash old_state = graph_->getProcessGroupState(); - GraphState graph_state = graph_->getState(); - const IdentifierHash requested_state = scc->request().process_group_state_.pg_state_name_; - scc->request().request_or_response_ = ControlClientCode::kSetStateSuccess; - - if (!graph_->isValidRunTarget(requested_state)) - { - // Reject before this can reach Graph::startTransition() with no matching node (#541). - scc->request().request_or_response_ = ControlClientCode::kSetStateInvalidArguments; - } - else if (GraphState::kInTransition == graph_state) - { - if (old_state != requested_state) - { - (void)graph_->setPendingState(requested_state); - // get state transition start time stamp - graph_->setRequestStartTime(); - graph_->cancel(); - } - else - { - // already in transition to the requested state - // pg->cancel(); - scc->request().request_or_response_ = ControlClientCode::kSetStateTransitionToSameState; - } - } - else if (GraphState::kSuccess == graph_state && old_state == requested_state) - { - // Already in state - scc->request().request_or_response_ = ControlClientCode::kSetStateAlreadyInState; - } - else - { - (void)graph_->setPendingState(requested_state); - // get state transition start time stamp - graph_->setRequestStartTime(); - } - graph_->updateCancelMessage(); - graph_->setStateManager(scc->request().originating_control_client_); -} - -void ProcessGroupManager::processGetExecutionError(ControlClientChannelP scc) -{ - // This is a synchronous call at the client side, but it's treated just like all the others, - // sending the response on the response channel. (The Control Client library will have to hide - // a future in the interface implementation) - std::shared_ptr pg = getProcessGroup(scc->request().process_group_state_.pg_name_); - - if (!pg) - { - // Error, unknown process group - scc->request().request_or_response_ = ControlClientCode::kExecutionErrorInvalidArguments; - } - else if (pg->getState() != GraphState::kUndefinedState) - { - // Error, process group not in an undefined state - scc->request().request_or_response_ = ControlClientCode::kExecutionErrorRequestFailed; - } - else - { - scc->request().execution_error_code_ = pg->getLastExecutionError(); - scc->request().request_or_response_ = ControlClientCode::kExecutionErrorRequestSuccess; - } -} - -void ProcessGroupManager::processGetInitialMachineStateTransitionResult(ControlClientChannelP scc) -{ - // If the process group is not valid or we have requested the result the maximum number of times - // we immediately return an error. Otherwise, the response is deferred until later. - if (!graph_ || ((1UL << (sizeof(scc->initial_result_count_) * 8UL)) - 1UL == scc->initial_result_count_)) - { - // We know immediately that there is a failure - scc->request().request_or_response_ = ControlClientCode::kInitialMachineStateNotSet; - } - else - { - scc->initial_result_count_++; - } -} - void ProcessGroupManager::processGroupHandler(Graph& pg) { // check to see if there is a state change request to process @@ -678,9 +469,6 @@ void ProcessGroupManager::processGroupHandler(Graph& pg) // at the moment graph is not running... // i.e. it is not in kInTransition, kAborting or kCancelled state // - // if there was a pending request, it was processed in the previous if statement - // but it resulted in ControlClientCode::kSetStateInvalidArguments error - // // in short, graph is in an error state (kUndefinedState) // and there is no valid request from outside, to change this situation... // @@ -702,12 +490,6 @@ void ProcessGroupManager::processGroupHandler(Graph& pg) } } -void ProcessGroupManager::setInitialStateTransitionResult(ControlClientCode result) -{ - initial_state_transition_result_ = result; - ControlClientChannel::nudgeControlClientHandler(); -} - ProcessInfoNode* ProcessGroupManager::getProcessInfoNode(uint32_t pg_index, IdentifierHash process_id) { if (pg_index == 0U && graph_) 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 2926d69dc5..82e15447d5 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 @@ -23,11 +23,9 @@ #include "score/mw/launch_manager/common/constants.hpp" #include "score/mw/launch_manager/common/identifier_hash.hpp" #include "score/mw/launch_manager/configuration/config.hpp" -#include "score/mw/launch_manager/control/control_client_channel.hpp" #include "score/mw/launch_manager/osal/wait_for_file.hpp" #include "score/mw/launch_manager/process_group_manager/details/component_event_queue.hpp" #include "score/mw/launch_manager/process_group_manager/details/graph.hpp" -#include "score/mw/launch_manager/process_group_manager/details/itransition_result_publisher.hpp" #include "score/mw/launch_manager/process_group_manager/details/os_handler.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_info_node.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" @@ -38,6 +36,7 @@ #include "score/mw/launch_manager/recovery_client/recovery_client.hpp" #include "score/mw/launch_manager/supervision_control_client/isupervision_control_notifier.hpp" #include "score/mw/launch_manager/watchdog/IWatchdogIf.hpp" +#include "score/mw/lifecycle/details/lm_control_service.h" namespace score::mw::lifecycle::internal { @@ -55,7 +54,7 @@ namespace score::mw::lifecycle::internal /// configured by integrator. Interaction with OSAL to start and stop processes. Interaction with OSAL to discover /// when processes terminated in an unexpected way. Fulfilling PG State transitions requests from SM, as well as /// informing SM about unexpected problems (for example process crashes). -class ProcessGroupManager final : public ITransitionResultPublisher +class ProcessGroupManager final { using WorkerQueue = MPMCConcurrentQueue, static_cast(ProcessLimits::kMaxProcesses)>; @@ -94,8 +93,8 @@ class ProcessGroupManager final : public ITransitionResultPublisher void deinitialize(); /// @brief Self-initiates the state transition to MainPG::Startup (Machine State Startup), then enters - /// and remains in a loop polling state managers and process groups using the `controlClientHandler()` - /// and `processGroupHandler()` methods until SIGINT or SIGTERM is received, then transitions all the + /// and remains in a loop polling state managers and process groups using the + /// `processGroupHandler()` methods until SIGINT or SIGTERM is received, then transitions all the /// process groups to the "Off" state before returning. Each time a piece of work is serviced, wait on /// the semaphore so as not to consume cpu cycles unduly. /// @return Returns true if the process group manager ran successfully, false otherwise. @@ -112,16 +111,6 @@ class ProcessGroupManager final : public ITransitionResultPublisher /// @return nullptr if the node does not exist, otherwise a pointer to the corresponding node. ProcessInfoNode* getProcessInfoNode(uint32_t pg_index, IdentifierHash process_id); - /// @brief set the initial machine group state change result, called by graph when the transition completes - /// @param result the result to save; it can only be saved once - void setInitialStateTransitionResult(ControlClientCode result) override; - - /// @brief Send a response message to a Control Client - /// @param msg the message to send, containing the Control Client id as the address to send it - /// @return true when either no error or the state manager no longer exists, false when the state manager had not - /// read the previous response - bool sendResponse(ControlClientMessage msg); - /// @brief Gets the process interface. /// @return Pointer to the OSAL process interface. osal::IProcess* getProcessInterface(); @@ -140,34 +129,9 @@ class ProcessGroupManager final : public ITransitionResultPublisher const IdentifierHash recovery_state_{"fallback"}; private: - /// @brief Perform the function of Control Client handler - /// @details (a) check for requests from any state manager processes in this process group\n - /// (b) check to see if the process group has a pending response to send to a state manager - /// @param pg Reference of the process group to check - void controlClientHandler(Graph& pg); - - /// @brief Check for requests from any state managers in this process group - /// @details If there is a request, process it and acknowledge the request with the - /// correct response code for success or error. Any state managers in the - /// process group may be found by following the links, starting at node0. - /// It's always necessary to check the Control Client channel pointer for validity, - /// as a process may terminate at any point, invalidating the pointer. - /// finally, check to see if the state manager is expecting any responses about the result of the - /// initial state transition, and if it is, it is able to accept a message and the transition result - /// is available, send it. - /// @note The requesting state manager must be saved in the process group that - /// a valid request is given for. - /// @param pg Reference of the process group (Graph) to check for state managers - void controlClientRequests(Graph& pg); - - /// @brief Check for any responses to send to the state manager(s) for this process group - /// @note If there is a pending event and a response may be sent, then a message is created - /// for that event. If the cancel message has a code other than 'kNotSet', then the cancel - /// message will also be sent. - /// @note If a response is not sent, because the message buffer is full, then it is left - /// pending to be checked the next time around the loop. - /// @param pg Reference of the process group (Graph) to check for pending responses - void controlClientResponses(Graph& pg); + /// @brief Start offering the service for control clients to connect to. + /// @details The service offer will be destroyed once the return value goes out of scope. + [[nodiscard]] LmControlSkeleton offerService(); /// @brief Handle a single recovery request emitted by Alive supervision. void handleRecoveryRequest(const IdentifierHash& process_identifier); @@ -196,28 +160,8 @@ class ProcessGroupManager final : public ITransitionResultPublisher /// @return true if the initial transition was started, false otherwise bool startInitialTransition(); - /// @brief Process a state transition request\n - /// @details Retrieve a pointer to the graph for the process group with the given name. \n - /// If the pointer is null:\n - /// set the request code in the message to `kSetStateInvalidArguments` \n - /// else:\n - /// if the process group is already in transition to the required state:\n - /// call the `cancel()` method of the graph - /// set the request code in the message to `kSetStateTransitionToSameState`\n - /// else if the process group is in transition to some other state:\n - /// call the `setPendingState()` method of the graph to set the new required state,\n - /// call the `cancel()` method of the graph and\n - /// set the request code of the message to `kSetStateSuccess`\n - /// else if the process group is already in the requested state:\n - /// set the request code of the message to `kSetStateAlreadyInState`\n - /// else:\n - /// call the `setPendingState()` method of the graph to set the new required state and\n - /// set the request code of the message to `kSetStateSuccess`\n - /// call the `setStateManager()` method of the graph to record the originating Control Client\n - /// @note If `kSetStateSuccess` is returned, state manager will expect a response later that - /// will set the promise, otherwise state manager will be able to set the promise immediately. - /// @param scc pointer to Control Client channel - void processStateTransition(ControlClientChannelP scc); + /// @brief Process a state transition request + void processStateTransition(); /// @brief process a get execution error request /// @details If the process group given in the `process_group_state_` exists:\n @@ -228,8 +172,7 @@ class ProcessGroupManager final : public ITransitionResultPublisher /// set the request code of the message to `kExecutionErrorRequestFailed`\n /// else:\n /// set the request code of the message to `kExecutionErrorInvalidArguments` - /// @param scc pointer to Control Client channel - void processGetExecutionError(ControlClientChannelP scc); + void processGetExecutionError(); /// @brief process a request to get the initial machine state transition result /// @details if `graph_` is a null pointer:\n @@ -237,8 +180,7 @@ class ProcessGroupManager final : public ITransitionResultPublisher /// else:\n /// wait for `initial_state_transition_result_` to be not equal to `kInitialMachineStateNotSet`\n /// set the request code of the message to be equal to `initial_state_transition_result_` - /// @param scc pointer to Control Client channel - void processGetInitialMachineStateTransitionResult(ControlClientChannelP scc); + void processGetInitialMachineStateTransitionResult(); /// @brief Send all process groups to the "Off" state /// @details cancel any Graph for a process group not in the "Off" state, wait for up to 2 seconds for all graphs @@ -256,9 +198,6 @@ class ProcessGroupManager final : public ITransitionResultPublisher /// @brief Creates process component objects, including the job queue and worker threads. void createProcessComponentsObjects(std::size_t total_processes); - /// @brief Initializes the Control Client handler. - bool initializeControlClientHandler(); - /// @brief The configuration object associated with the ProcessGroupManager. configuration::Config configuration_; @@ -277,9 +216,6 @@ class ProcessGroupManager final : public ITransitionResultPublisher /// @brief Shared pointer to the job queue for ProcessInfoNode jobs. std::shared_ptr worker_jobs_; - /// @brief The result of the initial state transition - std::atomic initial_state_transition_result_{ControlClientCode::kInitialMachineStateNotSet}; - /// @brief Pointer to the gaph. std::shared_ptr graph_{nullptr}; diff --git a/score/launch_manager/src/lifecycle_client/src/details/report_running_impl.cpp b/score/launch_manager/src/lifecycle_client/src/details/report_running_impl.cpp index df2289603d..3f8d8f8948 100644 --- a/score/launch_manager/src/lifecycle_client/src/details/report_running_impl.cpp +++ b/score/launch_manager/src/lifecycle_client/src/details/report_running_impl.cpp @@ -78,11 +78,8 @@ score::Result ReportRunningImpl::reportKRunningtoDaemon() const return comms_error; } - const bool correct_type = - sync->comms_type_ == CommsType::kReporting || sync->comms_type_ == CommsType::kControlClient; - // This is our best safeguard against incorrect data treated as an IPCCommsSync - if (!correct_type || sync->pid_ != getpid()) + if (sync->comms_type_ != CommsType::kReporting || sync->pid_ != getpid()) { LM_LOG_ERROR() << "[Lifecycle client] Cannot report kRunning from a non-reporting process or a process not " "started by Launch Manager"; diff --git a/score/launch_manager/src/lm_control/src/details/lm_control_impl.hpp b/score/launch_manager/src/lm_control/src/details/lm_control_impl.hpp index ef9591342a..1e515cfe60 100644 --- a/score/launch_manager/src/lm_control/src/details/lm_control_impl.hpp +++ b/score/launch_manager/src/lm_control/src/details/lm_control_impl.hpp @@ -206,8 +206,15 @@ class BasicLmControlImpl final : public ILmControl { return score::MakeUnexpected(ExecErrc::kInvalidArguments); } - std::lock_guard lock{callback_mutex_}; - callback_ = std::move(callback); + + { + std::lock_guard lock{callback_mutex_}; + callback_ = std::move(callback); + } + + // Check for any pending events from before the handler was installed. + onActivationResult(); + return {}; } diff --git a/scripts/config_mapping/tests/full_config_test/expected_output/lm_config_gen.json b/scripts/config_mapping/tests/full_config_test/expected_output/lm_config_gen.json index 16a8645ac0..4208534bbd 100644 --- a/scripts/config_mapping/tests/full_config_test/expected_output/lm_config_gen.json +++ b/scripts/config_mapping/tests/full_config_test/expected_output/lm_config_gen.json @@ -202,7 +202,7 @@ "component_properties": { "binary_name": "sm", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "is_self_terminating": false, "alive_supervision": { "reporting_cycle": 0.1, diff --git a/scripts/config_mapping/tests/full_config_test/input/lm_config.json b/scripts/config_mapping/tests/full_config_test/input/lm_config.json index 33f2c2e701..60dc293c57 100644 --- a/scripts/config_mapping/tests/full_config_test/input/lm_config.json +++ b/scripts/config_mapping/tests/full_config_test/input/lm_config.json @@ -173,7 +173,7 @@ "component_properties": { "binary_name": "sm", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "reporting_cycle": 0.1, "failed_cycles_tolerance": 0, diff --git a/scripts/config_mapping/tests/smoke_test/expected_output/lm_config_gen.json b/scripts/config_mapping/tests/smoke_test/expected_output/lm_config_gen.json index 70a4325aa6..78bda2d6c4 100644 --- a/scripts/config_mapping/tests/smoke_test/expected_output/lm_config_gen.json +++ b/scripts/config_mapping/tests/smoke_test/expected_output/lm_config_gen.json @@ -267,7 +267,7 @@ "component_properties": { "binary_name": "sm", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "is_self_terminating": false, "alive_supervision": { "reporting_cycle": 0.5, diff --git a/scripts/config_mapping/tests/smoke_test/input/lm_config.json b/scripts/config_mapping/tests/smoke_test/input/lm_config.json index ab8402597a..752d1fd024 100644 --- a/scripts/config_mapping/tests/smoke_test/input/lm_config.json +++ b/scripts/config_mapping/tests/smoke_test/input/lm_config.json @@ -119,7 +119,7 @@ "component_properties": { "binary_name": "sm", "application_profile": { - "application_type": "State_Manager" + "application_type": "Reporting_And_Supervised" }, "depends_on": ["setup_filesystem_sh"] }, diff --git a/scripts/config_mapping/unit_tests.py b/scripts/config_mapping/unit_tests.py index b8eb8cce71..df01c99911 100644 --- a/scripts/config_mapping/unit_tests.py +++ b/scripts/config_mapping/unit_tests.py @@ -341,7 +341,9 @@ def test_preprocessing_alive_supervision_presence_based_on_app_type(): }, "sm_app": { "component_properties": { - "application_profile": {"application_type": "State_Manager"} + "application_profile": { + "application_type": "Reporting_And_Supervised" + } } }, }, diff --git a/tests/integration/complex_monitoring/complex_monitoring.json b/tests/integration/complex_monitoring/complex_monitoring.json index b8e3ed2cf4..2d67aa1d3b 100644 --- a/tests/integration/complex_monitoring/complex_monitoring.json +++ b/tests/integration/complex_monitoring/complex_monitoring.json @@ -40,7 +40,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/complex_monitoring/component_complex_monitoring.cpp b/tests/integration/complex_monitoring/component_complex_monitoring.cpp index c42b5face1..6dcfdd3cea 100644 --- a/tests/integration/complex_monitoring/component_complex_monitoring.cpp +++ b/tests/integration/complex_monitoring/component_complex_monitoring.cpp @@ -67,7 +67,7 @@ TEST(ComplexMonitoring, ComponentComplexMonitoring) int main() { TestRunner(__FILE__, TerminationBehavior::kContinue).RunTests(); - // Then expect kill due to recovery action (verified by control client) + // Then expect kill due to recovery action (verified by state manager) while (true) // Stop reporting, wait for sigkill { pause(); diff --git a/tests/integration/complex_monitoring/control_client_test_driver.cpp b/tests/integration/complex_monitoring/control_client_test_driver.cpp index f742cef2cc..3c77c1508a 100644 --- a/tests/integration/complex_monitoring/control_client_test_driver.cpp +++ b/tests/integration/complex_monitoring/control_client_test_driver.cpp @@ -14,37 +14,76 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include +using namespace score::mw::lifecycle; + TEST(ComplexMonitoring, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - ASSERT_TRUE(check_clean({fallback_file})); + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } + TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Launch monitored process") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_complex_monitoring").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target run_target_complex_monitoring failed: " - << result.error().Message(); + const auto result = client->activate_run_target("run_target_complex_monitoring", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } - // Wait for health monitoring to fail and recovery to trigger - sleep(2); - TEST_STEP("Verify state changed to fallback run target") + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget run_target_complex_monitoring") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_complex_monitoring"); + } + }); + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget fallback") + { + EXPECT_EQ(source, RunTargetActivationSource::kRecoveryAction); + EXPECT_EQ(target, "fallback"); + } + }); + + TEST_STEP("Verify fallback run target was activated") { - // workaround to detect we're in fallback + // This verifies that a fallback process was actually started - the launch manager + // did not just send an event without taking the action. EXPECT_TRUE(std::filesystem::exists(fallback_file)) << "Fallback run target was not activated"; } + TEST_STEP("Activate Off run target") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/crash_ignores_dependents/BUILD b/tests/integration/crash_ignores_dependents/BUILD index 039993802c..b2c44d8e14 100644 --- a/tests/integration/crash_ignores_dependents/BUILD +++ b/tests/integration/crash_ignores_dependents/BUILD @@ -38,7 +38,6 @@ integration_test( name = "crash_ignores_dependents", srcs = ["crash_ignores_dependents.py"], binaries = [ - ":config", ":test_process", ":process_crashing_once", "//score/launch_manager", diff --git a/tests/integration/crash_on_startup/control_client_test_driver.cpp b/tests/integration/crash_on_startup/control_client_test_driver.cpp index 7c05214e64..bf505b345a 100644 --- a/tests/integration/crash_on_startup/control_client_test_driver.cpp +++ b/tests/integration/crash_on_startup/control_client_test_driver.cpp @@ -14,20 +14,43 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include +using namespace score::mw::lifecycle; + TEST(CrashOnStartup, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - ASSERT_TRUE(check_clean({crashCountPath(1), crashCountPath(2), crashCountPath(3), fallback_file})); + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } + TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + // Given a process that crashes on startup n times, but is configured to retry n times - so it eventually // succeeds. The behaviour is identical for the different crash counts, so it is parameterized over the // corresponding run targets. Each run target's process persists its crash count in its own file, so the @@ -37,12 +60,19 @@ TEST(CrashOnStartup, ControlClientTestDriver) { TEST_STEP(std::string{"Launch "} + std::string{run_target}) { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget(run_target).Get(stop_token); - // Then, the LM should restart it and eventually succeed - EXPECT_TRUE(result.has_value()) << "Activating " << run_target << " failed: " << result.error().Message(); + const auto result = client->activate_run_target(score::mw::lifecycle::RunTargetName{run_target}, true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + // Then, the LM should restart it and eventually succeed + pop_event([&run_target](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP(std::string{"Callback for RunTarget "} + std::string{run_target}) + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, run_target); + } + }); + TEST_STEP("Verify fallback run target was not activated, i.e. process eventually started successfully") { EXPECT_FALSE(std::filesystem::exists(fallback_file)) << "Fallback run target should not be activated yet"; @@ -52,22 +82,29 @@ TEST(CrashOnStartup, ControlClientTestDriver) // Given a process that crashes on startup but is not allowed to retry (number_of_attempts=0) TEST_STEP("Attempt to launch process crashing on startup without retries") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_crash_on_startup_once_but_no_retries").Get(stop_token); - EXPECT_FALSE(result.has_value()) - << "Expected run_target_crash_on_startup_once_but_no_retries activation to fail"; + const auto result = client->activate_run_target("run_target_crash_on_startup_once_but_no_retries", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } - // Limitation: we cannot wait for the transition to fallback to complete - sleep(1); - // Then, the LM should exhaust retries and trigger the fallback + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget fallback") + { + EXPECT_EQ(source, RunTargetActivationSource::kRecoveryAction); + EXPECT_EQ(target, "fallback"); + } + }); + TEST_STEP("Verify fallback run target was activated") { + // This verifies that a fallback process was actually started - the launch manager + // did not just send an event without taking the action. EXPECT_TRUE(std::filesystem::exists(fallback_file)) << "Fallback run target should have been activated"; } TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/crash_on_startup/crash_on_startup.json b/tests/integration/crash_on_startup/crash_on_startup.json index 96685ae08a..7752554d57 100644 --- a/tests/integration/crash_on_startup/crash_on_startup.json +++ b/tests/integration/crash_on_startup/crash_on_startup.json @@ -29,7 +29,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/fallback_to_same_target_restarts/BUILD b/tests/integration/fallback_to_same_target_restarts/BUILD index 9c39ec7713..140d95d8a7 100644 --- a/tests/integration/fallback_to_same_target_restarts/BUILD +++ b/tests/integration/fallback_to_same_target_restarts/BUILD @@ -38,7 +38,6 @@ integration_test( name = "fallback_to_same_target_restarts", srcs = ["fallback_to_same_target_restarts.py"], binaries = [ - ":config", ":control_client_test_driver", ":process_crashing_once", "//score/launch_manager", diff --git a/tests/integration/fallback_to_same_target_restarts/control_client_test_driver.cpp b/tests/integration/fallback_to_same_target_restarts/control_client_test_driver.cpp index 2acad50df1..e0c4da123c 100644 --- a/tests/integration/fallback_to_same_target_restarts/control_client_test_driver.cpp +++ b/tests/integration/fallback_to_same_target_restarts/control_client_test_driver.cpp @@ -14,11 +14,13 @@ #include "tests/utils/test_helper/test_helper.hpp" #include -#include +#include #include #include #include +using namespace score::mw::lifecycle; + // Given a correct configuration with: // - An initial Run Target named "Startup" containing "control_client_test_driver" // - A Run Target named "run_target_crashing_app_on_runtime" containing "control_client_test_driver" and @@ -26,24 +28,30 @@ TEST(FallbackToSameTargetRestarts, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - const std::string_view process_file = "process_started_normally"; ASSERT_TRUE(check_clean({process_file})); - // Establish communication with launch manager + + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } TEST_STEP("Start crashing process") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_crashing_app_on_runtime").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target run_target_crashing_app_on_runtime failed: " - << result.error().Message(); + const auto result = client->activate_run_target("run_target_crashing_app_on_runtime", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + // When the process crashes, wait for the fallback to be activated. { const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); @@ -57,9 +65,11 @@ TEST(FallbackToSameTargetRestarts, ControlClientTestDriver) { EXPECT_TRUE(std::filesystem::exists(process_file)) << "Process did not restart successfully"; } + TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/fallback_to_same_target_restarts/fallback_to_same_target_restarts.json b/tests/integration/fallback_to_same_target_restarts/fallback_to_same_target_restarts.json index f09aa5af15..65e98caf9f 100644 --- a/tests/integration/fallback_to_same_target_restarts/fallback_to_same_target_restarts.json +++ b/tests/integration/fallback_to_same_target_restarts/fallback_to_same_target_restarts.json @@ -46,7 +46,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/lm_shutdown_during_rt_switch/control_client_test_driver.cpp b/tests/integration/lm_shutdown_during_rt_switch/control_client_test_driver.cpp index 0117badf65..f959dcd055 100644 --- a/tests/integration/lm_shutdown_during_rt_switch/control_client_test_driver.cpp +++ b/tests/integration/lm_shutdown_during_rt_switch/control_client_test_driver.cpp @@ -16,9 +16,11 @@ #include "common.hpp" #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include +using namespace score::mw::lifecycle; + // The Launch Manager shall exit after performing a shutdown - stopping all the // processes it owns in dependency order - when requested (i.e. when it receives // a SIGTERM). A shutdown request takes priority over an in-progress run-target @@ -32,19 +34,51 @@ // run_target_c, must never start) and shut everything down. TEST(LmShutdownDuringRtSwitch, ControlClient) { - score::mw::lifecycle::ControlClient client{}; ASSERT_TRUE(check_clean({a_started, a_terminating, c_started})); + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Activate run_target_a") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_a").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating run_target_a failed: " << result.error().Message(); + const auto result = client->activate_run_target("run_target_a", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget run_target_a") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_a"); + } + }); + + TEST_STEP("Verify activation of run_target_a") + { EXPECT_TRUE(std::filesystem::exists(a_started)) << "component_a was not started"; } @@ -54,7 +88,8 @@ TEST(LmShutdownDuringRtSwitch, ControlClient) // external SIGTERM to the launch manager, so we must not wait for a // result. The launch manager will shut this process down instead of ever // completing the switch. - client.ActivateRunTarget("run_target_c"); + const auto result = client->activate_run_target("run_target_c", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } // Block until the launch manager terminates us as part of its own shutdown. diff --git a/tests/integration/lm_shutdown_during_rt_switch/lm_shutdown_during_rt_switch.json b/tests/integration/lm_shutdown_during_rt_switch/lm_shutdown_during_rt_switch.json index 9376cba98b..866b92ef18 100644 --- a/tests/integration/lm_shutdown_during_rt_switch/lm_shutdown_during_rt_switch.json +++ b/tests/integration/lm_shutdown_during_rt_switch/lm_shutdown_during_rt_switch.json @@ -46,7 +46,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/lm_shutdown_during_switch_to_off/control_client_test_driver.cpp b/tests/integration/lm_shutdown_during_switch_to_off/control_client_test_driver.cpp index d96d5f3432..2fe18d1342 100644 --- a/tests/integration/lm_shutdown_during_switch_to_off/control_client_test_driver.cpp +++ b/tests/integration/lm_shutdown_during_switch_to_off/control_client_test_driver.cpp @@ -16,9 +16,11 @@ #include "common.hpp" #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include +using namespace score::mw::lifecycle; + // The Launch Manager shall exit after performing a shutdown - stopping all the // processes it owns in dependency order - when requested (i.e. when it receives // a SIGTERM). @@ -35,22 +37,51 @@ // launch manager must end up stopping everything it owns and exit cleanly. TEST(LmShutdownDuringSwitchToOff, ControlClient) { - score::mw::lifecycle::ControlClient client{}; ASSERT_TRUE(check_clean({a_started, a_terminating})); + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } - const auto pid = getpid(); - const std::string step_msg = "Report running with pid == " + std::to_string(pid); + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } - TEST_STEP(step_msg) + TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Activate run_target_a") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_a").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating run_target_a failed: " << result.error().Message(); + const auto result = client->activate_run_target("run_target_a", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget run_target_a") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_a"); + } + }); + + TEST_STEP("Verify activation of run_target_a") + { EXPECT_TRUE(std::filesystem::exists(a_started)) << "component_a was not started"; } @@ -60,7 +91,8 @@ TEST(LmShutdownDuringSwitchToOff, ControlClient) // control client too (it is not part of "Off"), so we must not wait for a // result. The launch manager will shut this process down as part of the // switch to Off. - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } // Block until the launch manager terminates us as part of its own shutdown. diff --git a/tests/integration/lm_shutdown_during_switch_to_off/lm_shutdown_during_switch_to_off.json b/tests/integration/lm_shutdown_during_switch_to_off/lm_shutdown_during_switch_to_off.json index ac67c6af7a..1329999644 100644 --- a/tests/integration/lm_shutdown_during_switch_to_off/lm_shutdown_during_switch_to_off.json +++ b/tests/integration/lm_shutdown_during_switch_to_off/lm_shutdown_during_switch_to_off.json @@ -46,7 +46,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/parallel_launch/control_client_test_driver.cpp b/tests/integration/parallel_launch/control_client_test_driver.cpp index 376fd1e4b0..d544f37f45 100644 --- a/tests/integration/parallel_launch/control_client_test_driver.cpp +++ b/tests/integration/parallel_launch/control_client_test_driver.cpp @@ -17,7 +17,7 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include namespace @@ -41,37 +41,73 @@ bool wait_for_file(const std::filesystem::path& file, std::chrono::seconds timeo } } // namespace +using namespace score::mw::lifecycle; + TEST(ParallelLaunch, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - for (const auto id : kComponentIds) { ASSERT_TRUE(check_clean({"start_" + std::string{id}, "running_" + std::string{id}})); } + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } + TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Launch parallel run target") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_parallel_launch").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target run_target_parallel_launch failed: " - << result.error().Message(); + const auto result = client->activate_run_target("run_target_parallel_launch", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget run_target_parallel_launch") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_parallel_launch"); + } + }); + // Activate Run Target Startup again, to be sure that the termination of all components has been finished and the // files and its timestamps can be evaluated in the next test step. TEST_STEP("Activate Startup run target again") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("Startup").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target Startup failed: " << result.error().Message(); + const auto result = client->activate_run_target("Startup", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Verify all components started before any reported running") { std::filesystem::file_time_type max_start = std::filesystem::file_time_type::min(); @@ -95,7 +131,8 @@ TEST(ParallelLaunch, ControlClientTestDriver) TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/parallel_launch/parallel_launch.json b/tests/integration/parallel_launch/parallel_launch.json index 76b651457d..9750a6cd21 100644 --- a/tests/integration/parallel_launch/parallel_launch.json +++ b/tests/integration/parallel_launch/parallel_launch.json @@ -30,7 +30,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/process_complex_rep_failure/control_client_test_driver.cpp b/tests/integration/process_complex_rep_failure/control_client_test_driver.cpp index df4efbea26..a7b32214d2 100644 --- a/tests/integration/process_complex_rep_failure/control_client_test_driver.cpp +++ b/tests/integration/process_complex_rep_failure/control_client_test_driver.cpp @@ -13,7 +13,7 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include // Given a correct configuration with: @@ -26,44 +26,73 @@ // containing "control_client_test_driver" and // "component_does_not_report_krunning_in_time" -TEST(RecoveryActionComplexRepFailure, ControlClientTestDriver) -{ - score::mw::lifecycle::ControlClient client; +using namespace score::mw::lifecycle; +TEST(RecoveryActionSimpleRepFailure, ControlClientTestDriver) +{ ASSERT_TRUE(check_clean({fallback_file})); - // Establish communication with launch manager - TEST_STEP("Report running from ControlClientTestDriver") + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } + + TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } - // Start the run target run_target_app_does_report_krunning_in_time + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Activate RunTarget run_target_app_does_report_krunning_in_time") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_app_does_report_krunning_in_time").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target run_target_app_does_report_krunning_in_time " - "failed: " - << result.error().Message(); + const auto result = client->activate_run_target("run_target_app_does_report_krunning_in_time", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } - // Limitation: we cannot wait for the transition to fallback to complete - sleep(1); - // Then, the LM should continue without triggering the fallback + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget run_target_app_does_report_krunning_in_time") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_app_does_report_krunning_in_time"); + } + }); + TEST_STEP("Verify fallback run target has not been activated") { EXPECT_FALSE(std::filesystem::exists(fallback_file)) << "Fallback run target should have not been activated"; } - // Start the run target run_target_app_does_not_report_krunning_in_time + TEST_STEP("Activate RunTarget run_target_app_does_not_report_krunning_in_time") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_app_does_not_report_krunning_in_time").Get(stop_token); - EXPECT_FALSE(result.has_value()) << "Activating target run_target_app_does_not_report_krunning_in_time " - "did not fail as expected."; + const auto result = client->activate_run_target("run_target_app_does_not_report_krunning_in_time", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } - // Limitation: we cannot wait for the transition to fallback to complete - sleep(1); - // Then, the LM should exhaust retries and trigger the fallback + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget fallback") + { + EXPECT_EQ(source, RunTargetActivationSource::kRecoveryAction); + EXPECT_EQ(target, "fallback"); + } + }); + TEST_STEP("Verify fallback run target was activated") { EXPECT_TRUE(std::filesystem::exists(fallback_file)) << "Fallback run target should have been activated"; @@ -71,7 +100,8 @@ TEST(RecoveryActionComplexRepFailure, ControlClientTestDriver) TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/process_complex_rep_failure/process_complex_rep_failure.json b/tests/integration/process_complex_rep_failure/process_complex_rep_failure.json index cf53f9be84..754c4ae8b2 100644 --- a/tests/integration/process_complex_rep_failure/process_complex_rep_failure.json +++ b/tests/integration/process_complex_rep_failure/process_complex_rep_failure.json @@ -46,7 +46,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/process_crash_monitoring/BUILD b/tests/integration/process_crash_monitoring/BUILD index 43865f42b1..b42a5cb514 100644 --- a/tests/integration/process_crash_monitoring/BUILD +++ b/tests/integration/process_crash_monitoring/BUILD @@ -38,7 +38,6 @@ integration_test( name = "process_crash_monitoring", srcs = ["process_crash_monitoring.py"], binaries = [ - ":config", ":control_client_test_driver", ":process_crashing_on_runtime", "//score/launch_manager", diff --git a/tests/integration/process_crash_monitoring/control_client_test_driver.cpp b/tests/integration/process_crash_monitoring/control_client_test_driver.cpp index 5d9a0a1aa7..db2d82cb17 100644 --- a/tests/integration/process_crash_monitoring/control_client_test_driver.cpp +++ b/tests/integration/process_crash_monitoring/control_client_test_driver.cpp @@ -14,7 +14,7 @@ #include "tests/utils/test_helper/test_helper.hpp" #include -#include +#include #include #include #include @@ -24,42 +24,73 @@ // - A Run Target named "run_target_crashing_app_on_runtime" containing "control_client_test_driver" and // "component_crashing_on_runtime" +using namespace score::mw::lifecycle; + TEST(ProcessCrashMonitoring, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - ASSERT_TRUE(check_clean({fallback_file})); - // Establish communication with launch manager - TEST_STEP("Report running") + + std::unique_ptr client; + + TEST_STEP("Create client") { - score::mw::lifecycle::report_running(); + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); } - TEST_STEP("Start crashing process") + TEST_STEP("Register callback") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_crashing_app_on_runtime").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target run_target_crashing_app_on_runtime failed: " - << result.error().Message(); + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); } - // When the process crashes, wait for the fallback to be activated. - // Use polling instead of a fixed sleep so the test is robust under slow builds (e.g. TSan). + + TEST_STEP("Report running") { - const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); - while (!std::filesystem::exists(fallback_file) && std::chrono::steady_clock::now() < deadline) + report_running(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); } + }); + + TEST_STEP("Start crashing process") + { + const auto result = client->activate_run_target("run_target_crashing_app_on_runtime", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } - // Then + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget run_target_crashing_app_on_runtime") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_crashing_app_on_runtime"); + } + }); + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget fallback") + { + EXPECT_EQ(source, RunTargetActivationSource::kRecoveryAction); + EXPECT_EQ(target, "fallback"); + } + }); + TEST_STEP("Verify state changed to fallback run target") { - // workaround to detect we're in fallback + // This verifies that a fallback process was actually started - the launch manager + // did not just send an event without taking the action. EXPECT_TRUE(std::filesystem::exists(fallback_file)) << "Fallback run target was not activated"; } + TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/process_crash_monitoring/process_crash_monitoring.json b/tests/integration/process_crash_monitoring/process_crash_monitoring.json index 70afc4f850..8b0958ea45 100644 --- a/tests/integration/process_crash_monitoring/process_crash_monitoring.json +++ b/tests/integration/process_crash_monitoring/process_crash_monitoring.json @@ -46,7 +46,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/process_fd_leak/BUILD b/tests/integration/process_fd_leak/BUILD deleted file mode 100644 index 33e79c1606..0000000000 --- a/tests/integration/process_fd_leak/BUILD +++ /dev/null @@ -1,73 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -load("@rules_cc//cc:cc_binary.bzl", "cc_binary") -load("//tests/utils/bazel:integration.bzl", "integration_test") - -cc_binary( - name = "native", - srcs = [ - "common.hpp", - "get_fds.hpp", - "native.cpp", - ], - target_compatible_with = ["@platforms//os:linux"], - deps = [ - "//tests/utils/test_helper", - "@googletest//:gtest_main", - "@score_baselibs//score/mw/log", - ], -) - -cc_binary( - name = "reporting", - srcs = [ - "common.hpp", - "get_fds.hpp", - "reporting.cpp", - ], - target_compatible_with = ["@platforms//os:linux"], - deps = [ - "//score/launch_manager:lifecycle_cc", - "//tests/utils/test_helper", - "@googletest//:gtest_main", - ], -) - -cc_binary( - name = "control_client_test_driver", - srcs = [ - "common.hpp", - "control_client_test_driver.cpp", - "get_fds.hpp", - ], - target_compatible_with = ["@platforms//os:linux"], - deps = [ - "//score/launch_manager:control_cc", - "//score/launch_manager:lifecycle_cc", - "//tests/utils/test_helper", - "@googletest//:gtest_main", - "@score_baselibs//score/mw/log", - ], -) - -integration_test( - name = "process_fd_leak", - srcs = ["process_fd_leak.py"], - binaries = [ - ":control_client_test_driver", - ":native", - ":reporting", - "//score/launch_manager", - ], - config = ":process_fd_leak.json", -) diff --git a/tests/integration/process_fd_leak/common.hpp b/tests/integration/process_fd_leak/common.hpp deleted file mode 100644 index 7cbc6e8082..0000000000 --- a/tests/integration/process_fd_leak/common.hpp +++ /dev/null @@ -1,28 +0,0 @@ -/******************************************************************************** - * 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 PROCESS_FD_LEAK_HPP_ -#define PROCESS_FD_LEAK_HPP_ - -#include - -// Macros for consistent, constexpr names of marker files across test processes - -#define PROC_FILES(x) constexpr std::string_view x##_terminating = "proc_" #x "_terminating"; - -PROC_FILES(native) -PROC_FILES(reporting) - -#undef PROC_FILES - -#endif // PROCESS_FD_LEAK_HPP_ diff --git a/tests/integration/process_fd_leak/control_client_test_driver.cpp b/tests/integration/process_fd_leak/control_client_test_driver.cpp deleted file mode 100644 index 80611473e3..0000000000 --- a/tests/integration/process_fd_leak/control_client_test_driver.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************** - * 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 - -#include -#include -#include -#include - -#include "common.hpp" -#include "get_fds.hpp" -#include "tests/utils/test_helper/test_helper.hpp" -#include -#include - -int g_argc; -char** g_argv; - -TEST(ControlClientFDs, FindOpenFDs) -{ - - TEST_STEP("Before Running") - { - auto open_fds = get_fds(); - EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/ipc_shared_mem[0-9]+"))); - EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/_nudge~._.~me_"))); - std::ostringstream oss; - oss << open_fds; - EXPECT_TRUE(open_fds.empty()) << "Found open files!\n" << oss.str(); - } - - score::mw::lifecycle::report_running(); - - TEST_STEP("After Running") - { - auto open_fds = get_fds(); - EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/ipc_shared_mem[0-9]+"))); - EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/_nudge~._.~me_"))); - std::ostringstream oss; - oss << open_fds; - EXPECT_TRUE(open_fds.empty()) << "Found open files!\n" << oss.str(); - } - - score::mw::lifecycle::ControlClient client{}; - - TEST_STEP("After Control Client") - { - auto open_fds = get_fds(); - EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/ipc_shared_mem[0-9]+"))); - EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/_nudge~._.~me_"))); - std::ostringstream oss; - oss << open_fds; - EXPECT_TRUE(open_fds.empty()) << "Found open files!\n" << oss.str(); - } - - TEST_STEP("Wait for other procs to finish") - { - while (!std::filesystem::exists(reporting_terminating) || !std::filesystem::exists(native_terminating)) - { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - EXPECT_EQ(kill(getppid(), SIGTERM), 0); - } -} - -int main(int argc, char** argv) -{ - g_argc = argc; - g_argv = argv; - - // test end is signalled in the test so that we block to wait for other - // procs to finish - TestRunner runner{__FILE__, TerminationBehavior::kWait, TerminationNotification::kNone}; - - auto result = runner.RunTests(); - - return result; -} diff --git a/tests/integration/process_fd_leak/get_fds.hpp b/tests/integration/process_fd_leak/get_fds.hpp deleted file mode 100644 index 1b6c1ded57..0000000000 --- a/tests/integration/process_fd_leak/get_fds.hpp +++ /dev/null @@ -1,180 +0,0 @@ -/******************************************************************************** - * 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 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef GET_FDS_HPP_ -#define GET_FDS_HPP_ - -inline std::ostream& operator<<(std::ostream& outstream, const std::vector>& data) -{ - for (auto& [fd, path] : data) - { - outstream << "(FD: " << fd << ") " << path << "\n"; - } - return outstream; -} - -/// @brief Given the list of FDs and their paths, removes entries whose path -/// matches the regex. -/// @return AssertionSuccess if at least one entry matched and was removed, -/// AssertionFailure (with the full FD list) if nothing matched. -inline testing::AssertionResult filter_fd( - std::vector>& data, - std::regex&& path_regex) -{ - std::smatch m; - bool found{false}; - - auto it = data.begin(); - while (it != data.end()) - { - if (std::regex_search(it->second, m, path_regex)) - { - found = true; - it = data.erase(it); - } - else - { - ++it; - } - } - - if (!found) - { - std::ostringstream oss; - oss << data; - return testing::AssertionFailure() << "Regex did not match any open FD path. Open FDs:\n" << oss.str(); - } - return testing::AssertionSuccess(); -} - -/// @brief Returns a list of all open FDs, ignoring stdin, stdout & stderr. -/// @note This opens another FD for the /proc/self/fd directory however this is -/// not returned in the list. -inline std::vector> get_fds() -{ -#ifdef __QNXNTO__ - std::vector> out_vector{}; - - char proc_as_path[] = "/proc/self/as"; - int proc_fd = ::open(proc_as_path, O_RDONLY | O_NONBLOCK); - if (proc_fd == -1) - { - return out_vector; - } - - procfs_info proc_info{}; - if (::devctl(proc_fd, DCMD_PROC_INFO, &proc_info, sizeof(proc_info), nullptr) != EOK) - { - ::close(proc_fd); - return out_vector; - } - - constexpr std::size_t path_buf_size = sizeof(procfs_fdinfo) + PATH_MAX; - alignas(procfs_fdinfo) char buf[path_buf_size]; - - for (int fd_number = 0; fd_number < proc_info.num_fds; ++fd_number) - { - // skip irrelevant FDs - if (fd_number == STDIN_FILENO || fd_number == STDOUT_FILENO || fd_number == STDERR_FILENO || - fd_number == proc_fd) - { - continue; - } - - std::memset(buf, 0, path_buf_size); - procfs_fdinfo* info = reinterpret_cast(buf); - info->fd = fd_number; - - if (::devctl(proc_fd, DCMD_PROC_FDINFO, info, path_buf_size, nullptr) != EOK) - { - continue; - } - - auto& emplace_it = out_vector.emplace_back(static_cast(fd_number), std::string{}); - - if (info->path[0] != '\0') - { - emplace_it.second = info->path; - } - else - { - emplace_it.second = "Could not get real path"; - } - } - - ::close(proc_fd); - return out_vector; -#else - constexpr std::string_view fd_dir_path{"/proc/self/fd"}; - std::vector> out_vector{}; - - DIR* fd_dir = ::opendir(fd_dir_path.begin()); - if (fd_dir == nullptr) - { - return out_vector; - } - int fd_dir_fd = dirfd(fd_dir); - - int fd_number{0}; - - for (dirent* entry = ::readdir(fd_dir); entry != nullptr; entry = ::readdir(fd_dir)) - { - auto result = std::from_chars(entry->d_name, std::next(entry->d_name, std::strlen(entry->d_name)), fd_number); - if (result.ec != std::errc{}) - { - continue; - } - - // skip irelevant FDs - if (fd_number == STDIN_FILENO || fd_number == STDOUT_FILENO || fd_number == STDERR_FILENO || - fd_number == fd_dir_fd) - { - continue; - } - - auto& emplace_it = out_vector.emplace_back(static_cast(fd_number), std::string(PATH_MAX, 'a')); - - const std::string fd_path = std::string{fd_dir_path} + "/" + entry->d_name; - ssize_t len = ::readlink(fd_path.c_str(), emplace_it.second.data(), emplace_it.second.size()); - - if (len <= 0) - { - emplace_it.second = "Could not get real path"; - } - else - { - emplace_it.second.resize(len); - } - } - - ::closedir(fd_dir); - return out_vector; -#endif //__QNXNTO__ -} - -#endif // GET_FDS_HPP_ diff --git a/tests/integration/process_fd_leak/native.cpp b/tests/integration/process_fd_leak/native.cpp deleted file mode 100644 index 90fa2873d0..0000000000 --- a/tests/integration/process_fd_leak/native.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************** - * 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 - -#include - -#include "common.hpp" -#include "get_fds.hpp" -#include "tests/utils/test_helper/test_helper.hpp" - -int g_argc; -char** g_argv; - -TEST(NativeFDs, FindOpenFDs) -{ - auto open_fds = get_fds(); - std::ostringstream oss; - oss << open_fds; - EXPECT_TRUE(open_fds.empty()) << "Found open files!\n" << oss.str(); - - ASSERT_TRUE(touch_file(native_terminating)); -} - -int main(int argc, char** argv) -{ - g_argc = argc; - g_argv = argv; - - TestRunner runner{__FILE__, TerminationBehavior::kContinue, TerminationNotification::kNone}; - - return runner.RunTests(); -} diff --git a/tests/integration/process_fd_leak/process_fd_leak.json b/tests/integration/process_fd_leak/process_fd_leak.json deleted file mode 100644 index d16703e296..0000000000 --- a/tests/integration/process_fd_leak/process_fd_leak.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "schema_version": 1, - "defaults": { - "deployment_config": { - "bin_dir": "/tmp/tests/process_fd_leak", - "ready_timeout": 1.0, - "shutdown_timeout": 1.0, - "ready_recovery_action": { - "restart": { - "number_of_attempts": 0 - } - }, - "recovery_action": { - "switch_run_target": { - "run_target": "fallback_run_target" - } - }, - "environmental_variables": { - "LD_LIBRARY_PATH": "/opt/lib" - }, - "sandbox": { - "uid": 0, - "gid": 0, - "scheduling_policy": "SCHED_OTHER", - "scheduling_priority": 0 - } - }, - "component_properties": { - "application_profile": { - "application_type": "Native", - "is_self_terminating": true, - "alive_supervision": { - "min_indications": 0 - } - }, - "ready_condition": { - "process_state": "Running" - } - } - }, - "components": { - "native": { - "component_properties": { - "binary_name": "native" - } - }, - "control_client_test_driver": { - "component_properties": { - "binary_name": "control_client_test_driver", - "application_profile": { - "application_type": "State_Manager", - "is_self_terminating": false - } - } - }, - "reporting": { - "component_properties": { - "binary_name": "reporting", - "application_profile": { - "application_type": "Reporting" - } - } - } - }, - "run_targets": { - "Startup": { - "depends_on": [ - "native", - "control_client_test_driver", - "reporting" - ] - } - }, - "initial_run_target": "Startup", - "alive_supervision": { - "evaluation_cycle": 0.05 - }, - "fallback_run_target": { - "depends_on": [] - } -} diff --git a/tests/integration/process_fd_leak/process_fd_leak.py b/tests/integration/process_fd_leak/process_fd_leak.py deleted file mode 100644 index 5e3d068fab..0000000000 --- a/tests/integration/process_fd_leak/process_fd_leak.py +++ /dev/null @@ -1,38 +0,0 @@ -# ******************************************************************************* -# 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 -# ******************************************************************************* -from tests.utils.testing_utils.setup_test import setup_test -from tests.utils.testing_utils.run_test import run_test -from tests.utils.testing_utils.test_results import assert_test_results -from attribute_plugin import add_test_properties - - -@add_test_properties( - fully_verifies=[], - test_type="resource-usage", - derivation_technique="explorative-testing", -) -def test_process_fd_leak(target, setup_test, assert_test_results, remote_test_dir): - """Tests the inherited file descriptors from LCM for Native, Reporting and - State_Manager application types.""" - - run_test( - target=target, - binary_path=str(remote_test_dir / "launch_manager"), - args=["-c", str(remote_test_dir / "etc/process_fd_leak.bin")], - cwd=str(remote_test_dir), - ) - - # That the process is started and an XML file is produced verifies feat_req__lifecycle__launch_support - assert_test_results( - {"native.xml", "control_client_test_driver.xml", "reporting.xml"} - ) diff --git a/tests/integration/process_fd_leak/reporting.cpp b/tests/integration/process_fd_leak/reporting.cpp deleted file mode 100644 index 44ad13f187..0000000000 --- a/tests/integration/process_fd_leak/reporting.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************** - * 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 - -#include - -#include "common.hpp" -#include "get_fds.hpp" -#include "tests/utils/test_helper/test_helper.hpp" -#include - -int g_argc; -char** g_argv; - -TEST(ReportingProcessFDs, FindOpenFDs) -{ - TEST_STEP("Before Running") - { - auto open_fds = get_fds(); - EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/ipc_shared_mem[0-9]+"))); - std::ostringstream oss; - oss << open_fds; - EXPECT_TRUE(open_fds.empty()) << "Found open files!\n" << oss.str(); - } - - score::mw::lifecycle::report_running(); - - TEST_STEP("After Running") - { - auto open_fds = get_fds(); - std::ostringstream oss; - oss << open_fds; - EXPECT_TRUE(open_fds.empty()) << "Found open files!\n" << oss.str(); - } - - ASSERT_TRUE(touch_file(reporting_terminating)); -} - -int main(int argc, char** argv) -{ - g_argc = argc; - g_argv = argv; - - TestRunner runner{__FILE__, TerminationBehavior::kContinue, TerminationNotification::kNone}; - - return runner.RunTests(); -} diff --git a/tests/integration/process_simple_rep_failure/control_client_test_driver.cpp b/tests/integration/process_simple_rep_failure/control_client_test_driver.cpp index 26ab7820b3..a7b32214d2 100644 --- a/tests/integration/process_simple_rep_failure/control_client_test_driver.cpp +++ b/tests/integration/process_simple_rep_failure/control_client_test_driver.cpp @@ -13,7 +13,7 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include // Given a correct configuration with: @@ -26,44 +26,73 @@ // containing "control_client_test_driver" and // "component_does_not_report_krunning_in_time" +using namespace score::mw::lifecycle; + TEST(RecoveryActionSimpleRepFailure, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - ASSERT_TRUE(check_clean({fallback_file})); - // Establish communication with launch manager - TEST_STEP("Report running from ControlClientTestDriver") + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } + + TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } - // Start the run target run_target_app_does_report_krunning_in_time + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Activate RunTarget run_target_app_does_report_krunning_in_time") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_app_does_report_krunning_in_time").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target run_target_app_does_report_krunning_in_time " - "failed: " - << result.error().Message(); + const auto result = client->activate_run_target("run_target_app_does_report_krunning_in_time", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } - // Limitation: we cannot wait for the transition to fallback to complete - sleep(1); - // Then, the LM should continue without triggering the fallback + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget run_target_app_does_report_krunning_in_time") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_app_does_report_krunning_in_time"); + } + }); + TEST_STEP("Verify fallback run target has not been activated") { EXPECT_FALSE(std::filesystem::exists(fallback_file)) << "Fallback run target should have not been activated"; } - // Start the run target run_target_app_does_not_report_krunning_in_time + TEST_STEP("Activate RunTarget run_target_app_does_not_report_krunning_in_time") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_app_does_not_report_krunning_in_time").Get(stop_token); - EXPECT_FALSE(result.has_value()) << "Activating target run_target_app_does_not_report_krunning_in_time " - "did not fail as expected."; + const auto result = client->activate_run_target("run_target_app_does_not_report_krunning_in_time", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } - // Limitation: we cannot wait for the transition to fallback to complete - sleep(1); - // Then, the LM should exhaust retries and trigger the fallback + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget fallback") + { + EXPECT_EQ(source, RunTargetActivationSource::kRecoveryAction); + EXPECT_EQ(target, "fallback"); + } + }); + TEST_STEP("Verify fallback run target was activated") { EXPECT_TRUE(std::filesystem::exists(fallback_file)) << "Fallback run target should have been activated"; @@ -71,7 +100,8 @@ TEST(RecoveryActionSimpleRepFailure, ControlClientTestDriver) TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/process_simple_rep_failure/process_simple_rep_failure.json b/tests/integration/process_simple_rep_failure/process_simple_rep_failure.json index dd443d306c..64e70af697 100644 --- a/tests/integration/process_simple_rep_failure/process_simple_rep_failure.json +++ b/tests/integration/process_simple_rep_failure/process_simple_rep_failure.json @@ -46,7 +46,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } @@ -139,6 +139,11 @@ } } }, + "fallback": { + "depends_on": [ + "control_client_test_driver" + ] + }, "Off": { "depends_on": [] } diff --git a/tests/integration/process_wrong_binary_failure/control_client_test_driver.cpp b/tests/integration/process_wrong_binary_failure/control_client_test_driver.cpp index 805fffd38b..9e5b29b67f 100644 --- a/tests/integration/process_wrong_binary_failure/control_client_test_driver.cpp +++ b/tests/integration/process_wrong_binary_failure/control_client_test_driver.cpp @@ -13,36 +13,61 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include +using namespace score::mw::lifecycle; + TEST(MissingBinaryFailure, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - ASSERT_TRUE(check_clean({fallback_file})); - TEST_STEP("Report kRunning from ControlClientTestDriver") + std::unique_ptr client; + + TEST_STEP("Create client") { - score::mw::lifecycle::report_running(); + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); } - TEST_STEP("Activate RunTarget containing a component with a missing binary") + TEST_STEP("Register callback") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_with_missing_binary").Get(stop_token); - EXPECT_FALSE(result.has_value()) << "Activating a run target with a missing binary should fail."; + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); } - // Limitation: we cannot wait for the transition to fallback to complete - sleep(1); - TEST_STEP("Verify fallback run target was activated") + + TEST_STEP("Report running") { - EXPECT_TRUE(std::filesystem::exists(fallback_file)) << "Fallback run target should have been activated"; + report_running(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + + TEST_STEP("Activate RunTarget containing a component with a missing binary") + { + const auto result = client->activate_run_target("run_target_with_missing_binary", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget fallback") + { + EXPECT_EQ(source, RunTargetActivationSource::kRecoveryAction); + EXPECT_EQ(target, "fallback"); + } + }); + TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/process_wrong_binary_failure/process_wrong_binary_failure.json b/tests/integration/process_wrong_binary_failure/process_wrong_binary_failure.json index a5e832b313..a6ebd4e256 100644 --- a/tests/integration/process_wrong_binary_failure/process_wrong_binary_failure.json +++ b/tests/integration/process_wrong_binary_failure/process_wrong_binary_failure.json @@ -46,7 +46,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } @@ -113,6 +113,11 @@ } } }, + "fallback": { + "depends_on": [ + "control_client_test_driver" + ] + }, "Off": { "depends_on": [] } diff --git a/tests/integration/ready_conditions/file_state/exists/control_client_test_driver.cpp b/tests/integration/ready_conditions/file_state/exists/control_client_test_driver.cpp index 79d028b100..53e09be94e 100644 --- a/tests/integration/ready_conditions/file_state/exists/control_client_test_driver.cpp +++ b/tests/integration/ready_conditions/file_state/exists/control_client_test_driver.cpp @@ -13,37 +13,75 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include +using namespace score::mw::lifecycle; + TEST(FileStateExist, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - ASSERT_TRUE(check_clean({fallback_file})); - TEST_STEP("Report kRunning from ControlClientTestDriver") + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") { - score::mw::lifecycle::report_running(); + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); } + TEST_STEP("Report running") + { + report_running(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Activate RunTarget that works") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("working").Get(stop_token); - EXPECT_TRUE(result.has_value()); + const auto result = client->activate_run_target("working", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget working") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "working"); + } + }); + TEST_STEP("Activate RunTarget that times out") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("timeout").Get(stop_token); - EXPECT_FALSE(result.has_value()) << "Activation should timeout and error"; + const auto result = client->activate_run_target("timeout", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget fallback") + { + EXPECT_EQ(source, RunTargetActivationSource::kRecoveryAction); + EXPECT_EQ(target, "fallback"); + } + }); + TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/ready_conditions/file_state/exists/ready_condition_file.json b/tests/integration/ready_conditions/file_state/exists/ready_condition_file.json index 06dc491453..14dc533505 100644 --- a/tests/integration/ready_conditions/file_state/exists/ready_condition_file.json +++ b/tests/integration/ready_conditions/file_state/exists/ready_condition_file.json @@ -35,7 +35,7 @@ "process_state": "Running" }, "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "is_self_terminating": false, "alive_supervision": { "min_indications": 0 @@ -125,6 +125,11 @@ "run_target": "fallback_run_target" } } + }, + "fallback": { + "depends_on": [ + "control_client_test_driver" + ] } }, "initial_run_target": "Startup", diff --git a/tests/integration/ready_conditions/file_state/not_existing/control_client_test_driver.cpp b/tests/integration/ready_conditions/file_state/not_existing/control_client_test_driver.cpp index a84861b462..2c1e7e83fc 100644 --- a/tests/integration/ready_conditions/file_state/not_existing/control_client_test_driver.cpp +++ b/tests/integration/ready_conditions/file_state/not_existing/control_client_test_driver.cpp @@ -13,37 +13,75 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include +using namespace score::mw::lifecycle; + TEST(FileStateNotExistng, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - ASSERT_TRUE(check_clean({fallback_file})); - TEST_STEP("Report kRunning from ControlClientTestDriver") + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") { - score::mw::lifecycle::report_running(); + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); } + TEST_STEP("Report running") + { + report_running(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Activate RunTarget that works") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("working").Get(stop_token); - EXPECT_TRUE(result.has_value()); + const auto result = client->activate_run_target("working", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget working") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "working"); + } + }); + TEST_STEP("Activate RunTarget that times out") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("timeout").Get(stop_token); - EXPECT_FALSE(result.has_value()) << "Activation should timeout and error"; + const auto result = client->activate_run_target("timeout", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget fallback") + { + EXPECT_EQ(source, RunTargetActivationSource::kRecoveryAction); + EXPECT_EQ(target, "fallback"); + } + }); + TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/ready_conditions/file_state/not_existing/ready_condition_file_not_existing.json b/tests/integration/ready_conditions/file_state/not_existing/ready_condition_file_not_existing.json index d3ac091859..6d31d9e186 100644 --- a/tests/integration/ready_conditions/file_state/not_existing/ready_condition_file_not_existing.json +++ b/tests/integration/ready_conditions/file_state/not_existing/ready_condition_file_not_existing.json @@ -37,7 +37,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager" + "application_type": "Reporting_And_Supervised" } } }, @@ -125,6 +125,11 @@ "run_target": "fallback_run_target" } } + }, + "fallback": { + "depends_on": [ + "control_client_test_driver" + ] } }, "initial_run_target": "Startup", diff --git a/tests/integration/rt_running_when_process_exits/control_client_test_driver.cpp b/tests/integration/rt_running_when_process_exits/control_client_test_driver.cpp index 2e08a24909..7588887a13 100644 --- a/tests/integration/rt_running_when_process_exits/control_client_test_driver.cpp +++ b/tests/integration/rt_running_when_process_exits/control_client_test_driver.cpp @@ -16,7 +16,7 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include namespace @@ -25,6 +25,8 @@ namespace constexpr std::string_view kSlowSetupOutput = "slow_setup_output.txt"; } // namespace +using namespace score::mw::lifecycle; + // Given a configuration with two run targets, each pulling in a self-terminating component whose // ready condition is "Terminated" but which differ in whether that component has a dependent: // @@ -40,33 +42,69 @@ constexpr std::string_view kSlowSetupOutput = "slow_setup_output.txt"; // running and its marker file has not been written yet. TEST(RtRunningWhenProcessExits, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - score::cpp::stop_token stop_token; - // kSlowSetupOutput is checked too: its later presence must be a reliable signal that // the slow setup component terminated during *this* run, not leftover from a previous one. ASSERT_TRUE(check_clean({kSlowSetupOutput})); + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } + TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + // The with-dependents case: filesystem_reader asserts on the prepared file and on the setup // script process being gone, so the ordering is checked there. TEST_STEP("Activate run target with a terminated-ready component that HAS a dependent") { - auto result = client.ActivateRunTarget("run_target_reader").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating run_target_reader failed: " << result.error(); + const auto result = client->activate_run_target("run_target_reader", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget run_target_reader") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_reader"); + } + }); + // The no-dependents case: activation must only complete once the slow setup component has terminated. TEST_STEP("Activate run target with a terminated-ready component that has NO dependent") { - auto result = client.ActivateRunTarget("run_target_slow_setup").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating run_target_slow_setup failed: " << result.error(); + const auto result = client->activate_run_target("run_target_slow_setup", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget run_target_slow_setup") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_slow_setup"); + } + }); + TEST_STEP("Verify the slow setup component had terminated before activation completed") { EXPECT_TRUE(std::filesystem::exists(kSlowSetupOutput)) @@ -77,7 +115,8 @@ TEST(RtRunningWhenProcessExits, ControlClientTestDriver) TEST_STEP("Activate run target Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/rt_running_when_process_exits/rt_running_when_process_exits.json b/tests/integration/rt_running_when_process_exits/rt_running_when_process_exits.json index 8fc9c76548..b86b155557 100644 --- a/tests/integration/rt_running_when_process_exits/rt_running_when_process_exits.json +++ b/tests/integration/rt_running_when_process_exits/rt_running_when_process_exits.json @@ -32,7 +32,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/shutdown_signal/control_client_test_driver.cpp b/tests/integration/shutdown_signal/control_client_test_driver.cpp index c308a5c2f8..44fe983b7f 100644 --- a/tests/integration/shutdown_signal/control_client_test_driver.cpp +++ b/tests/integration/shutdown_signal/control_client_test_driver.cpp @@ -18,9 +18,11 @@ #include "common.hpp" #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include +using namespace score::mw::lifecycle; + // The Launch Manager shall shut a process down by sending it a SIGTERM, and, if // the process does not terminate itself in time, a SIGKILL. // @@ -35,31 +37,67 @@ // would terminate the control daemon too, so it could not run the assertion. TEST(ShutdownSignal, Daemon) { - score::mw::lifecycle::ControlClient client{}; ASSERT_TRUE(check_clean({sigterm_received_file})); - TEST_STEP("Control daemon report running") + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") { - score::mw::lifecycle::report_running(); + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); } + TEST_STEP("Report running") + { + report_running(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Activate RunTarget Running") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("Running").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target Running failed: " << result.error().Message(); + const auto result = client->activate_run_target("Running", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Running") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "Running"); + } + }); + // Switching away from "Running" terminates shutdown_signal_process. Because it does not // self-terminate on SIGTERM, the Launch Manager must escalate to SIGKILL for // the transition to complete. TEST_STEP("Activate RunTarget Startup") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("Startup").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target Startup failed: " << result.error().Message(); + const auto result = client->activate_run_target("Startup", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Verify SIGTERM was received and SIGKILL forced termination") { // SIGTERM was delivered: the process recorded its PID before blocking. @@ -81,7 +119,8 @@ TEST(ShutdownSignal, Daemon) TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/shutdown_signal/shutdown_signal.json b/tests/integration/shutdown_signal/shutdown_signal.json index 0e1f8ace11..fdb6ed4f4e 100644 --- a/tests/integration/shutdown_signal/shutdown_signal.json +++ b/tests/integration/shutdown_signal/shutdown_signal.json @@ -23,7 +23,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/smoke/control_client_test_driver.cpp b/tests/integration/smoke/control_client_test_driver.cpp index 994ab59c43..ef88582f29 100644 --- a/tests/integration/smoke/control_client_test_driver.cpp +++ b/tests/integration/smoke/control_client_test_driver.cpp @@ -16,33 +16,101 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include +using namespace score::mw::lifecycle; + TEST(Smoke, Daemon) { - score::mw::lifecycle::ControlClient client{}; - TEST_STEP("Control daemon report running") + std::unique_ptr client; + + TEST_STEP("Create client") { - // report running - score::mw::lifecycle::report_running(); + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } + + TEST_STEP("Validate active run target") + { + const auto result = client->get_active_run_target(); + EXPECT_FALSE(result.has_value()) << "Should not be active until we report running"; + EXPECT_EQ(result.error(), ExecErrc::kActivationInProgress); + } + + TEST_STEP("Report running") + { + report_running(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + + TEST_STEP("Validate active run target") + { + const auto result = client->get_active_run_target(); + EXPECT_TRUE(result.has_value()) << result.error().Message(); + EXPECT_EQ(result.value(), "Startup"); } TEST_STEP("Activate RunTarget Running") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("Running").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target Running failed: " << result.error().Message(); + const auto result = client->activate_run_target("Running", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Running") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "Running"); + } + }); + + TEST_STEP("Validate active run target") + { + const auto result = client->get_active_run_target(); + EXPECT_TRUE(result.has_value()) << result.error().Message(); + EXPECT_EQ(result.value(), "Running"); } + TEST_STEP("Activate RunTarget Startup") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("Startup").Get(stop_token); - EXPECT_TRUE(result.has_value()); + const auto result = client->activate_run_target("Startup", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); + } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "Startup"); + } + }); + + TEST_STEP("Validate active run target") + { + const auto result = client->get_active_run_target(); + EXPECT_TRUE(result.has_value()) << result.error().Message(); + EXPECT_EQ(result.value(), "Startup"); } + TEST_STEP("Activate RunTarget Off") { - client.ActivateRunTarget("Off"); + const auto result = client->activate_run_target("Off", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } } diff --git a/tests/integration/smoke/lifecycle_smoketest.json b/tests/integration/smoke/lifecycle_smoketest.json index 4d5e830106..bdaf40923b 100644 --- a/tests/integration/smoke/lifecycle_smoketest.json +++ b/tests/integration/smoke/lifecycle_smoketest.json @@ -46,7 +46,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/integration/switch_run_target/control_client_test_driver.cpp b/tests/integration/switch_run_target/control_client_test_driver.cpp index 987097bdad..17f867c020 100644 --- a/tests/integration/switch_run_target/control_client_test_driver.cpp +++ b/tests/integration/switch_run_target/control_client_test_driver.cpp @@ -14,7 +14,7 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -#include +#include #include // Given a configuration with the following dependency tree: @@ -32,15 +32,40 @@ // component D is contained), *component* A only depends on component B. // Component E is not included in any run target, so it should never be launched. +using namespace score::mw::lifecycle; + TEST(SwitchRunTarget, ControlClientTestDriver) { - score::mw::lifecycle::ControlClient client; - ASSERT_TRUE(check_clean({a_started, b_started, d_started, e_started})); + + std::unique_ptr client; + + TEST_STEP("Create client") + { + auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance"); + ASSERT_TRUE(client_result.has_value()) << client_result.error().Message(); + client = std::move(client_result).value(); + } + + TEST_STEP("Register callback") + { + const auto result = client->register_run_target_activation_callback(push_event); + ASSERT_TRUE(result.has_value()); + } + TEST_STEP("Report running") { - score::mw::lifecycle::report_running(); + report_running(); } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation); + EXPECT_EQ(target, "Startup"); + } + }); + // When we switch run to run target A // Then // Processes A and B verify that B is started before A and terminated after A when switching run targets @@ -49,10 +74,18 @@ TEST(SwitchRunTarget, ControlClientTestDriver) TEST_STEP("Activate run target A") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("run_target_a").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target run_target_a failed: " << result.error().Message(); + const auto result = client->activate_run_target("run_target_a", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget A") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "run_target_a"); + } + }); + TEST_STEP("Verify running processes") { for (const auto proc : running_processes) @@ -61,13 +94,21 @@ TEST(SwitchRunTarget, ControlClientTestDriver) } } // Processes A and B verify that they have been shut down in the correct order. + TEST_STEP("Activate RunTarget Startup") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("Startup").Get(stop_token); - EXPECT_TRUE(result.has_value()) << "Activating target Startup failed: " << result.error().Message(); + const auto result = client->activate_run_target("Startup", true); + EXPECT_TRUE(result.has_value()) << result.error().Message(); } + pop_event([](RunTargetActivationSource source, RunTargetName target) { + TEST_STEP("Callback for RunTarget Startup") + { + EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest); + EXPECT_EQ(target, "Startup"); + } + }); + TEST_STEP("Verify terminated processes") { for (const auto proc : terminating_processes) @@ -84,16 +125,14 @@ TEST(SwitchRunTarget, ControlClientTestDriver) // Regression test for #541: an unrecognized run target used to crash the whole daemon. TEST_STEP("Activate an unrecognized run target") { - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("not_a_real_run_target").Get(stop_token); + const auto result = client->activate_run_target("not_a_real_run_target", true); EXPECT_FALSE(result.has_value()) << "Should be rejected, not silently accepted"; } TEST_STEP("Verify Launch Manager survived and remains responsive") { // Re-request "Startup" (already active) rather than switching again, to avoid restarting // component_a/component_b a second time. - score::cpp::stop_token stop_token; - auto result = client.ActivateRunTarget("Startup").Get(stop_token); + const auto result = client->activate_run_target("Startup", true); ASSERT_FALSE(result.has_value()) << "Expected an already-in-state rejection, got success"; EXPECT_NE(result.error().Message().find("already"), std::string_view::npos) << "Expected an 'already in state' rejection, got: " << result.error().Message(); diff --git a/tests/integration/switch_run_target/switch_run_target.json b/tests/integration/switch_run_target/switch_run_target.json index 1dd1ce0bd7..a98d621bf1 100644 --- a/tests/integration/switch_run_target/switch_run_target.json +++ b/tests/integration/switch_run_target/switch_run_target.json @@ -24,7 +24,7 @@ "component_properties": { "binary_name": "control_client_test_driver", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0 } diff --git a/tests/scripts/gen_lifecycle_config.py b/tests/scripts/gen_lifecycle_config.py index bd0c83b57d..a9cbc55a0c 100644 --- a/tests/scripts/gen_lifecycle_config.py +++ b/tests/scripts/gen_lifecycle_config.py @@ -77,7 +77,7 @@ def gen_lifecycle_config( "component_properties": { "binary_name": "control_app/control_daemon", "application_profile": { - "application_type": "State_Manager", + "application_type": "Reporting_And_Supervised", "alive_supervision": { "min_indications": 0, }, diff --git a/tests/utils/bazel/integration.bzl b/tests/utils/bazel/integration.bzl index be754b1947..ffa8d3da19 100644 --- a/tests/utils/bazel/integration.bzl +++ b/tests/utils/bazel/integration.bzl @@ -53,22 +53,25 @@ def integration_test( if config: launch_manager_config( - name = "config", + name = "lm_config", config = config, flatbuffer_out_dir = "etc", ) - all_files = files + [":config"] - else: - all_files = files + pkg_files( + name = "lm_config_file", + srcs = [":lm_config"], + prefix = "tests/{}".format(name), + attributes = pkg_attributes(mode = "0400"), + ) pkg_files( - name = "files", - srcs = all_files, - prefix = "tests/{}".format(name), + name = "mw_com_config_file", + srcs = ["//tests/utils/environments:mw_com_config.json"], + prefix = "tests/{}/etc".format(name), attributes = pkg_attributes(mode = "0400"), ) - pkg_tar(name = "environment", srcs = [":binaries", ":files"]) + pkg_tar(name = "environment", srcs = [":binaries", ":lm_config_file", ":mw_com_config_file"]) final_deps = kwargs.pop("deps", []) + all_requirements + [ "@score_tooling//python_basics/score_pytest:attribute_plugin", diff --git a/tests/utils/environments/BUILD b/tests/utils/environments/BUILD index bf049e0256..a7137fb655 100644 --- a/tests/utils/environments/BUILD +++ b/tests/utils/environments/BUILD @@ -15,3 +15,11 @@ exports_files( ["ecu_logging_config.json"], visibility = ["//tests/utils/environments:__subpackages__"], ) + +exports_files( + ["mw_com_config.json"], + visibility = [ + "//examples/demo_verification:__subpackages__", + "//tests/integration:__subpackages__", + ], +) diff --git a/tests/utils/environments/mw_com_config.json b/tests/utils/environments/mw_com_config.json new file mode 100644 index 0000000000..a478be5d74 --- /dev/null +++ b/tests/utils/environments/mw_com_config.json @@ -0,0 +1,100 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/score/mw/lifecycle/LmControlService", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 7101, + "events": [ + { + "eventName": "ActivationResult", + "eventId": 1 + } + ], + "methods": [ + { + "methodName": "ActivateRunTarget", + "methodId": 2 + }, + { + "methodName": "GetActiveRunTarget", + "methodId": 3 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "LaunchManager/StateManager/Instance", + "serviceTypeName": "/score/mw/lifecycle/LmControlService", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "ActivationResult", + "numberOfSampleSlots": 8, + "maxSubscribers": 1 + } + ], + "methods": [ + { + "methodName": "ActivateRunTarget", + "queueSize": 1 + }, + { + "methodName": "GetActiveRunTarget", + "queueSize": 1 + } + ] + } + ] + }, + { + "instanceSpecifier": "StateManager/LaunchManager/Instance", + "serviceTypeName": "/score/mw/lifecycle/LmControlService", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "ActivationResult" + } + ], + "methods": [ + { + "methodName": "ActivateRunTarget", + "queueSize": 1 + }, + { + "methodName": "GetActiveRunTarget", + "queueSize": 1 + } + ] + } + ] + } + ], + "global": { + "asil-level": "QM" + } +} diff --git a/tests/utils/test_helper/BUILD b/tests/utils/test_helper/BUILD index 788f1a299a..41f9d9eef1 100644 --- a/tests/utils/test_helper/BUILD +++ b/tests/utils/test_helper/BUILD @@ -29,6 +29,7 @@ cc_library( hdrs = ["test_helper.hpp"], visibility = ["//tests:__subpackages__"], deps = [ + "//score/launch_manager/src/lm_control", "@googletest//:gtest_main", ], ) diff --git a/tests/utils/test_helper/test_helper.hpp b/tests/utils/test_helper/test_helper.hpp index e11320ffa1..69ccc76a78 100644 --- a/tests/utils/test_helper/test_helper.hpp +++ b/tests/utils/test_helper/test_helper.hpp @@ -21,6 +21,8 @@ #include #include +#include + /// @return File path to an xml adjacent to the input file path inline std::string xmlPath(const std::string_view file) { @@ -173,4 +175,40 @@ class TestRunner } }; +using score::mw::lifecycle::RunTargetActivationSource; +using score::mw::lifecycle::RunTargetName; + +bool event_received = false; +RunTargetActivationSource event_source; +RunTargetName event_target; +std::mutex event_mutex; +std::condition_variable event_condition; + +void push_event(RunTargetActivationSource source, RunTargetName target) +{ + { + std::unique_lock event_lock(event_mutex); + event_condition.wait(event_lock, [&] { + return !event_received; + }); + event_received = true; + event_source = source; + event_target = target; + } + event_condition.notify_one(); +}; + +void pop_event(std::function callback) +{ + { + std::unique_lock event_lock(event_mutex); + event_condition.wait(event_lock, [&] { + return event_received; + }); + callback(event_source, event_target); + event_received = false; + } + event_condition.notify_one(); +}; + #endif // TESTS_UTILS_TEST_HELPER_HPP