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 0974b3d41e..75a6480f0a 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 @@ -23,6 +23,10 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ ":icomponent", + "//score/launch_manager/src/daemon/src/alive_monitor:ialive_supervision_handle", + "//score/launch_manager/src/daemon/src/alive_monitor:isupervision_factory", + "//score/launch_manager/src/daemon/src/common:log", + "//score/launch_manager/src/daemon/src/configuration:component_config", "//score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action:iforce_stop_action", "//score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition:iready_condition", "//score/launch_manager/src/daemon/src/process_group_manager/details/start_action:istart_action", @@ -37,6 +41,9 @@ lm_cc_test( deps = [ ":component", ":mock_component", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/alive_monitor:mock_alive_supervision_handle", + "//score/launch_manager/src/daemon/src/alive_monitor:mock_supervision_factory", "//score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action:mock_force_stop_action", "//score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition:mock_ready_condition", "//score/launch_manager/src/daemon/src/process_group_manager/details/start_action:mock_start_action", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index e10bab1cfc..87b458eaf1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -12,23 +12,73 @@ ********************************************************************************/ #include "score/mw/launch_manager/process_group_manager/details/component.hpp" +#include "score/mw/launch_manager/common/log.hpp" namespace score::mw::lifecycle::internal { +namespace +{ + +using score::mw::lifecycle::internal::configuration::ApplicationType; + +bool isSupervised(const ApplicationType& application_type) noexcept +{ + switch (application_type) + { + case ApplicationType::Native: + case ApplicationType::Reporting: + return false; + case ApplicationType::ReportingAndSupervised: + case ApplicationType::StateManager: + return true; + } + + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("All possible cases are covered"); +} + +std::unique_ptr constructSupervision( + configuration::ComponentConfig& config, + ISupervisionFactory& supervision_factory) +{ + if (!isSupervised(config.component_properties.application_profile.application_type)) + { + return nullptr; + } + + SCORE_LANGUAGE_FUTURECPP_ASSERT_DBG_MESSAGE( + app_profile.alive_supervision.has_value(), "Supervised component did not have alive supervision config"); + + LM_LOG_DEBUG() << "Setting up alive supervision for" << config.name; + std::unique_ptr handle = supervision_factory.constructSupervision( + IdentifierHash{config.name}, + config.deployment_config.sandbox.uid, + config.component_properties.application_profile.alive_supervision.value()); + if (!handle) + { + LM_LOG_ERROR() << "Failed to set up alive supervision for" << config.name; + return nullptr; + } + + config.deployment_config.environmental_variables.add("LCM_ALIVE_INTERFACE_PATH", handle->getConnectionId()); + return handle; +} + +} // namespace + Component::Component( const IStartAction& start_action, const IStopAction& stop_action, const IForceStopAction& force_stop_action, const IReadyCondition& ready_condition, - bool self_terminating, - IdentifierHash identifier) + ISupervisionFactory& supervision_factory, + configuration::ComponentConfig& config) : start_action_(start_action), stop_action_(stop_action), force_stop_action_(force_stop_action), ready_condition_(ready_condition), - self_terminating_(self_terminating), - identifier_(identifier), + config_(config), + supervision_handle_(constructSupervision(config, supervision_factory)), state_(TerminatedState{}) { } @@ -40,6 +90,13 @@ IComponent::RequestResult Component::activate(cpp::stop_token stop_token) SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( std::holds_alternative(state_), "Cannot activate component in a running state"); + if (isSupervised(config_.component_properties.application_profile.application_type) && !supervision_handle_) + { + LM_LOG_ERROR() << config_.name << ": alive supervision is not set up"; + state_ = FaultState{}; + return RequestResult::kFailure; + } + const Result start_result = start_action_.start(stop_token); if (!start_result.has_value()) { @@ -75,8 +132,16 @@ IComponent::RequestResult Component::activate(cpp::stop_token stop_token) // The state could change while the lock was released, so we have to check. if (const StartingState* const starting = std::get_if(&state_)) { - state_ = ReadyState{starting->handle_}; - return RequestResult::kSuccess; + if (activateSupervision()) + { + state_ = ReadyState{starting->handle_}; + return RequestResult::kSuccess; + } + else + { + state_ = FaultState{starting->handle_}; + return RequestResult::kFailure; + } } SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("If stop was not requested, then state must still be StartingState"); @@ -92,6 +157,12 @@ IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token) (starting != nullptr) || (ready != nullptr), "Cannot deactivate component in a non-running state"); const Handle& handle = (starting != nullptr) ? starting->handle_ : ready->handle_; + if (!deactivateSupervision()) + { + state_ = FaultState{handle}; + return RequestResult::kFailure; + } + // If the component is still waiting for ready conditions, cancel that. if (starting != nullptr) { @@ -131,14 +202,19 @@ IComponent::TerminationResult Component::tryHandleTermination(int32_t status) // Invalidate the resource to prevent acting on something which is already gone. invalidate(handle); + if (ready != nullptr && !deactivateSupervision()) + { + state_ = FaultState{handle}; + return TerminationResult::kFailure; + } + // Certain ready conditions can accept terminations. if (starting != nullptr && ready_condition_.tryHandleTermination(handle, status)) { return TerminationResult::kIgnore; } - // Self-terminating components can accept terminations. - if (self_terminating_ && status == 0) + if (config_.component_properties.application_profile.is_self_terminating && status == 0) { return TerminationResult::kIgnore; } @@ -156,7 +232,7 @@ IComponent::TerminationResult Component::tryHandleTermination(int32_t status) IdentifierHash Component::getIdentifier() const { - return identifier_; + return IdentifierHash{config_.name}; } bool Component::active() const @@ -165,4 +241,52 @@ bool Component::active() const return std::holds_alternative(state_); } +bool Component::activateSupervision() const +{ + if (!supervision_handle_) + { + return true; + } + + timespec timestamp{}; + + if (clock_gettime(CLOCK_MONOTONIC, ×tamp) != 0) + { + LM_LOG_ERROR() << config_.name << ": failed to get timestamp to activate supervision:" << strerror(errno); + return false; + } + + if (!supervision_handle_->activateSupervision(timestamp)) + { + LM_LOG_ERROR() << config_.name << ": failed to activate supervision"; + return false; + } + + return true; +} + +bool Component::deactivateSupervision() const +{ + if (!supervision_handle_) + { + return true; + } + + timespec timestamp{}; + + if (clock_gettime(CLOCK_MONOTONIC, ×tamp) != 0) + { + LM_LOG_ERROR() << config_.name << ": failed to get timestamp to deactivate supervision: " << strerror(errno); + return false; + } + + if (!supervision_handle_->deactivateSupervision(timestamp)) + { + LM_LOG_ERROR() << config_.name << ": failed to deactivate supervision"; + return false; + } + + return true; +} + } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 98eaede06e..1ea4d10f12 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -14,6 +14,9 @@ #ifndef SCORE_LCM_COMPONENT_HPP_INCLUDED #define SCORE_LCM_COMPONENT_HPP_INCLUDED +#include "score/mw/launch_manager/alive_monitor/ialive_supervision_handle.hpp" +#include "score/mw/launch_manager/alive_monitor/isupervision_factory.hpp" +#include "score/mw/launch_manager/configuration/component_config.hpp" #include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" #include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" #include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" @@ -29,20 +32,20 @@ namespace score::mw::lifecycle::internal class Component final : public IComponent { public: - /// @brief Creates a new component from the given actions. + /// @brief Creates a new component from its configuration and the given actions. /// @param start_action How to start the resource. /// @param stop_action How to gracefully stop the resource. /// @param force_stop_action How to forcefully stop the resource. /// @param ready_condition How to decide when the resource has finished its startup. - /// @param self_terminating Whether the resource is allowed to terminate on its own. - /// @param identifier Name of the component. + /// @param supervision_factory Factory used to construct alive supervision. + /// @param config Configuration of the component. Component( const IStartAction& start_action, const IStopAction& stop_action, const IForceStopAction& force_stop_action, const IReadyCondition& ready_condition, - bool self_terminating = false, - IdentifierHash identifier = IdentifierHash{}); + ISupervisionFactory& supervision_factory, + configuration::ComponentConfig& config); /// @brief Start the resource and set the component to active. /// @param stop_token Token which can be used to interrupt the activation. @@ -80,11 +83,11 @@ class Component final : public IComponent /// @brief How to decide when the resource has finished its startup. const IReadyCondition& ready_condition_; - /// @brief Whether the resource is allowed to terminate on its own. - const bool self_terminating_; + /// @brief Configuration of the component. + const configuration::ComponentConfig& config_; - /// @brief Name of the component. - const IdentifierHash identifier_; + /// @brief Alive supervision of the resource, or nullptr if it is not supervised. + const std::unique_ptr supervision_handle_; struct TerminatedState final { @@ -118,6 +121,12 @@ class Component final : public IComponent /// @brief The current state of the component. State state_; + + /// @brief Start alive supervision now, if the component is supervised. + [[nodiscard]] bool activateSupervision() const; + + /// @brief Stop alive supervision now, if the component is supervised. + [[nodiscard]] bool deactivateSupervision() const; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index 5c875efcdb..9b7fb70fc4 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -11,11 +11,14 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +#include "score/mw/launch_manager/alive_monitor/mock_alive_supervision_handle.hpp" +#include "score/mw/launch_manager/alive_monitor/mock_supervision_factory.hpp" #include "score/mw/launch_manager/process_group_manager/details/component.hpp" #include "score/mw/launch_manager/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp" #include "score/mw/launch_manager/process_group_manager/details/ready_condition/mock_ready_condition.hpp" #include "score/mw/launch_manager/process_group_manager/details/start_action/mock_start_action.hpp" #include "score/mw/launch_manager/process_group_manager/details/stop_action/mock_stop_action.hpp" +#include "score/mw/lifecycle/execution_error.h" #include #include #include @@ -57,7 +60,9 @@ TEST(ComponentTest, StartSucceeds) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); const auto result = component.activate(cpp::stop_token{}); @@ -70,7 +75,9 @@ TEST(ComponentTest, StartActionCalled) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); EXPECT_CALL(start_action, start(_)).WillOnce(Return(Result{mock_handle})); @@ -83,7 +90,9 @@ TEST(ComponentTest, StopActionCalled) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(stop_action, stop(_, _)) @@ -99,7 +108,9 @@ TEST(ComponentTest, DeactivateReturnsSuccessWhenStopActionReturnsSuccess) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestResult::kSuccess)); @@ -116,7 +127,9 @@ TEST(ComponentTest, DeactivateReturnsFailureWhenStopActionFails) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestResult::kFailure)); @@ -133,7 +146,9 @@ TEST(ComponentTest, ReadyConditionCalled) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(ready_condition, wait(_, _)) @@ -148,7 +163,9 @@ TEST(ComponentTest, TerminationDuringStartupInterruptsActivation) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&component](cpp::stop_token token, const Handle) { @@ -170,7 +187,9 @@ TEST(ComponentTest, StartSetsActive) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); static_cast(component.activate(cpp::stop_token{})); @@ -183,7 +202,9 @@ TEST(ComponentTest, StopClearsActive) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); static_cast(component.activate(cpp::stop_token{})); static_cast(component.deactivate(cpp::stop_token{})); @@ -197,7 +218,9 @@ TEST(ComponentTest, CannotDeactivateInTerminatedState) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); EXPECT_DEATH( { static_cast(component.deactivate(cpp::stop_token{})); }, @@ -210,7 +233,9 @@ TEST(ComponentTest, CannotActivateInReadyState) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -226,7 +251,9 @@ TEST(ComponentTest, CannotActivateInTerminatingState) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestResult::kSuccess)); @@ -244,7 +271,9 @@ TEST(ComponentTest, CannotDeactivateInTerminatingState) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestResult::kSuccess)); @@ -263,7 +292,9 @@ TEST(ComponentTest, CannotActivateInFaultState) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -280,7 +311,9 @@ TEST(ComponentTest, CannotDeactivateInFaultState) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -298,7 +331,10 @@ TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsIgnored) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition, true); + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.is_self_terminating = true; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -314,7 +350,10 @@ TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsActive) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition, true); + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.is_self_terminating = true; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -330,7 +369,10 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsFailure) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition, true); + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.is_self_terminating = true; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -346,7 +388,10 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsNotActive) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition, true); + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.is_self_terminating = true; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -362,7 +407,10 @@ TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsIgnored) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition, true); + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.is_self_terminating = true; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -383,7 +431,10 @@ TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsActive) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition, true); + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.is_self_terminating = true; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) { @@ -402,7 +453,10 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsFailure) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition, true); + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.is_self_terminating = true; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -423,7 +477,10 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsNotActive) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition, true); + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.is_self_terminating = true; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) { @@ -442,7 +499,9 @@ TEST(ComponentTest, SettingBothStopTokens) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - Component component(start_action, stop_action, force_stop_action, ready_condition); + NiceMock factory; + configuration::ComponentConfig config; + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); cpp::stop_source stop_source{}; ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -461,5 +520,271 @@ TEST(ComponentTest, SettingBothStopTokens) EXPECT_EQ(result, IComponent::RequestResult::kFailure); } +TEST(ComponentTest, SupervisionConstructedFromConfig) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.name = "comp"; + config.deployment_config.sandbox.uid = 1000U; + config.component_properties.application_profile.application_type = + configuration::ApplicationType::ReportingAndSupervised; + config.component_properties.application_profile.alive_supervision = configuration::ComponentAliveSupervision{}; + config.component_properties.application_profile.alive_supervision->reporting_cycle_ms = 50U; + + EXPECT_CALL( + factory, + constructSupervision( + IdentifierHash{"comp"}, 1000U, Field(&configuration::ComponentAliveSupervision::reporting_cycle_ms, 50U))) + .WillOnce(Return(ByMove(std::unique_ptr{}))); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); +} + +TEST(ComponentTest, NoSupervisionConstructedWhenUnsupervised) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.application_type = configuration::ApplicationType::Reporting; + + EXPECT_CALL(factory, constructSupervision(_, _, _)).Times(0); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); + + EXPECT_EQ(config.deployment_config.environmental_variables.size(), 0U); +} + +TEST(ComponentTest, AliveInterfacePathAddedToEnvironment) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.application_type = + configuration::ApplicationType::ReportingAndSupervised; + config.component_properties.application_profile.alive_supervision = configuration::ComponentAliveSupervision{}; + + auto handle = std::make_unique>(); + ON_CALL(*handle, getConnectionId()).WillByDefault(Return(std::string_view{"/test_path"})); + ON_CALL(factory, constructSupervision(_, _, _)) + .WillByDefault(Return(ByMove(std::unique_ptr{std::move(handle)}))); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); + + ASSERT_EQ(config.deployment_config.environmental_variables.size(), 1U); + const auto& variable = *config.deployment_config.environmental_variables.begin(); + EXPECT_EQ(variable.key(), "LCM_ALIVE_INTERFACE_PATH"); + EXPECT_EQ(variable.value(), "/test_path"); +} + +TEST(ComponentTest, FailedSupervisionConstructionFailsActivation) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.application_type = + configuration::ApplicationType::ReportingAndSupervised; + config.component_properties.application_profile.alive_supervision = configuration::ComponentAliveSupervision{}; + + ON_CALL(factory, constructSupervision(_, _, _)) + .WillByDefault(Return(ByMove(std::unique_ptr{}))); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); + + const auto result = component.activate(cpp::stop_token{}); + + EXPECT_EQ(result, IComponent::RequestResult::kFailure); + EXPECT_EQ(config.deployment_config.environmental_variables.size(), 0U); +} + +TEST(ComponentTest, SupervisionActivatedWhenReady) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.application_type = + configuration::ApplicationType::ReportingAndSupervised; + config.component_properties.application_profile.alive_supervision = configuration::ComponentAliveSupervision{}; + + auto handle = std::make_unique>(); + auto* supervision = handle.get(); + ON_CALL(*supervision, getConnectionId()).WillByDefault(Return(std::string_view{"/test_path"})); + ON_CALL(factory, constructSupervision(_, _, _)) + .WillByDefault(Return(ByMove(std::unique_ptr{std::move(handle)}))); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(*supervision, activateSupervision(_)).WillOnce(Return(true)); + + static_cast(component.activate(cpp::stop_token{})); +} + +TEST(ComponentTest, SupervisionActivationFailureFaultsComponent) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.application_type = + configuration::ApplicationType::ReportingAndSupervised; + config.component_properties.application_profile.alive_supervision = configuration::ComponentAliveSupervision{}; + + auto handle = std::make_unique>(); + auto* supervision = handle.get(); + ON_CALL(*supervision, getConnectionId()).WillByDefault(Return(std::string_view{"/test_path"})); + ON_CALL(factory, constructSupervision(_, _, _)) + .WillByDefault(Return(ByMove(std::unique_ptr{std::move(handle)}))); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(*supervision, activateSupervision(_)).WillOnce(Return(false)); + + const auto result = component.activate(cpp::stop_token{}); + EXPECT_EQ(result, IComponent::RequestResult::kFailure); + EXPECT_FALSE(component.active()); + EXPECT_DEATH( + { static_cast(component.deactivate(cpp::stop_token{})); }, + "Cannot deactivate component in a non-running state"); +} + +TEST(ComponentTest, SupervisionNotActivatedWhenStartFails) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.application_type = + configuration::ApplicationType::ReportingAndSupervised; + config.component_properties.application_profile.alive_supervision = configuration::ComponentAliveSupervision{}; + + auto handle = std::make_unique>(); + auto* supervision = handle.get(); + ON_CALL(*supervision, getConnectionId()).WillByDefault(Return(std::string_view{"/test_path"})); + ON_CALL(factory, constructSupervision(_, _, _)) + .WillByDefault(Return(ByMove(std::unique_ptr{std::move(handle)}))); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{MakeUnexpected(ExecErrc::kGeneralError)})); + EXPECT_CALL(*supervision, activateSupervision(_)).Times(0); + + static_cast(component.activate(cpp::stop_token{})); +} + +TEST(ComponentTest, SupervisionDeactivatedImmediatelyOnTermination) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.application_type = + configuration::ApplicationType::ReportingAndSupervised; + config.component_properties.application_profile.alive_supervision = configuration::ComponentAliveSupervision{}; + + auto handle = std::make_unique>(); + auto* supervision = handle.get(); + ON_CALL(*supervision, getConnectionId()).WillByDefault(Return(std::string_view{"/test_path"})); + ON_CALL(factory, constructSupervision(_, _, _)) + .WillByDefault(Return(ByMove(std::unique_ptr{std::move(handle)}))); + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + InSequence sequence; + EXPECT_CALL(*supervision, activateSupervision(_)).WillOnce(Return(true)); + EXPECT_CALL(*supervision, deactivateSupervision(_)).WillOnce(Return(true)); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); + static_cast(component.activate(cpp::stop_token{})); + static_cast(component.tryHandleTermination(1)); + + // deactivate() is never called because there is nothing reading the event queue +} + +TEST(ComponentTest, SupervisionDeactivatedBeforeStopAction) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.application_type = + configuration::ApplicationType::ReportingAndSupervised; + config.component_properties.application_profile.alive_supervision = configuration::ComponentAliveSupervision{}; + + auto handle = std::make_unique>(); + auto* supervision = handle.get(); + ON_CALL(*supervision, getConnectionId()).WillByDefault(Return(std::string_view{"/test_path"})); + ON_CALL(factory, constructSupervision(_, _, _)) + .WillByDefault(Return(ByMove(std::unique_ptr{std::move(handle)}))); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + ON_CALL(*supervision, activateSupervision(_)).WillByDefault(Return(true)); + + static_cast(component.activate(cpp::stop_token{})); + + InSequence sequence; + EXPECT_CALL(*supervision, deactivateSupervision(_)).WillOnce(Return(true)); + EXPECT_CALL(stop_action, stop(_, _)).WillOnce(Return(IComponent::RequestResult::kSuccess)); + + static_cast(component.deactivate(cpp::stop_token{})); +} + +TEST(ComponentTest, SupervisionDeactivationFailureFaultsComponent) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + NiceMock factory; + configuration::ComponentConfig config; + config.component_properties.application_profile.application_type = + configuration::ApplicationType::ReportingAndSupervised; + config.component_properties.application_profile.alive_supervision = configuration::ComponentAliveSupervision{}; + + auto handle = std::make_unique>(); + auto* supervision = handle.get(); + ON_CALL(*supervision, getConnectionId()).WillByDefault(Return(std::string_view{"/test_path"})); + ON_CALL(factory, constructSupervision(_, _, _)) + .WillByDefault(Return(ByMove(std::unique_ptr{std::move(handle)}))); + + Component component(start_action, stop_action, force_stop_action, ready_condition, factory, config); + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(*supervision, activateSupervision(_)).WillOnce(Return(true)); + static_cast(component.activate(cpp::stop_token{})); + EXPECT_CALL(*supervision, deactivateSupervision(_)).WillOnce(Return(false)); + EXPECT_CALL(stop_action, stop(_, _)).Times(0); + + const auto result = component.deactivate(cpp::stop_token{}); + EXPECT_EQ(result, IComponent::RequestResult::kFailure); + EXPECT_FALSE(component.active()); + EXPECT_DEATH( + { static_cast(component.deactivate(cpp::stop_token{})); }, + "Cannot deactivate component in a non-running state"); +} + } // namespace } // namespace score::mw::lifecycle::internal