Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions score/launch_manager/docs/user_guide/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,21 @@ component_properties (object)
* **Allowed Values:**
* ``"Running"``: The process has started and reached its running state.
* ``"Terminated"``: The process has started, reached its running state, and then terminated successfully.
* **file_state** (object, optional)
Comment thread
MaciejKaszynski marked this conversation as resolved.
* **Description:** Specifies a ready condition based on the existence of a file at a given path.
* **Properties:**
Comment thread
MaciejKaszynski marked this conversation as resolved.
* **file_path** (string, required)
* **Description:** Specifies the absolute path to the file being watched.
* **state** (string, optional)
* **Description:** Specifies the required existence state of the file.
* **Allowed Values:**
* ``"Exists"``: The component is ready when the file at ``file_path`` exists.
* ``"NotExisting"``: The component is ready when the file at ``file_path`` does not exist.
* **Default:** ``"Exists"``
* **polling_interval** (number, optional)
* **Description:** Specifies the time interval, in seconds (e.g., ``0.3`` for 300 milliseconds), at which the **Launch Manager** checks the file existence state.
* **Constraint:** Must be greater than 0.
* **Default:** ``0.01``

.. _lm_conf_deployment_config_object_:

Expand Down
16 changes: 14 additions & 2 deletions score/launch_manager/src/daemon/src/configuration/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
#define CONFIG_HPP

#include <sys/types.h>
#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <variant>
#include <vector>

namespace score::mw::lifecycle::internal::configuration
Expand All @@ -37,6 +39,12 @@ enum class ProcessState : uint8_t
Terminated = 1
};

enum class FileExistenceState : uint8_t
{
Exists = 0,
NotExisting,
};

struct ComponentAliveSupervision
{
uint32_t reporting_cycle_ms{};
Expand All @@ -52,11 +60,15 @@ struct ApplicationProfile
std::optional<ComponentAliveSupervision> alive_supervision;
};

struct ReadyCondition
struct FileState
{
ProcessState process_state{ProcessState::Running};
std::string file_path;
FileExistenceState state{FileExistenceState::Exists};
std::chrono::milliseconds polling_interval{10};
};

using ReadyCondition = std::variant<ProcessState, FileState>;

struct ComponentProperties
{
std::string binary_name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,48 @@
"Terminated"
],
"description": "Specifies the required state of the component's POSIX process. 'Running': the process has started and reached its running state. 'Terminated': the process has started, reached its running state, and then terminated successfully."
},
"file_state": {
"type": "object",
"description": "Specifies a ready condition based on the existence of a file at a given path.",
"properties": {
"file_path": {
"type": "string",
Comment thread
MaciejKaszynski marked this conversation as resolved.
"pattern": "^/(?:[^/]+(?:/[^/]+)*)$",
"description": "Specifies the absolute path to the file being watched."
},
"state": {
"type": "string",
"enum": [
"Exists",
"NotExisting"
],
"description": "Specifies the required existence of the file. 'Exists': the file must be present at 'file_path'. 'NotExisting': the file must be absent from 'file_path'. Defaults to 'Exists' if not specified."
},
"polling_interval": {
"type": "number",
"exclusiveMinimum": 0,
"description": "Specifies the time interval, in seconds (e.g., '0.3' for 300 milliseconds), at which the Launch Manager checks the file existence. Defaults to 10 milliseconds."
}
},
"required": [
"file_path"
],
"additionalProperties": false
Comment thread
MaciejKaszynski marked this conversation as resolved.
}
},
"required": [],
"oneOf": [
{
"required": [
"process_state"
]
},
{
"required": [
"file_state"
]
}
],
"additionalProperties": false
}
},
Expand Down Expand Up @@ -488,4 +527,4 @@
"initial_run_target"
],
"additionalProperties": false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <algorithm>
#include <cassert>
#include <cstring>
#include <iostream>
#include <map>
#include <set>

Expand All @@ -30,7 +31,7 @@ namespace
constexpr const char* kAliveInterfaceEnvName = "LCM_ALIVE_INTERFACE_PATH";
constexpr uint32_t kDefaultProcessExecutionError = 1U;

uint64_t defaultProcessorAffinityMask()
[[maybe_unused]] uint64_t defaultProcessorAffinityMask()
{
return (1ULL << score::mw::lifecycle::internal::osal::getNumCores()) - 1ULL;
}
Expand Down Expand Up @@ -202,19 +203,34 @@ DependencyList ConfigurationAdapter::buildDependencyList(const ComponentProperti

for (const auto& dep_name : props.depends_on)
{
auto dep_it = component_by_name_.find(dep_name);
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(
dep_it != component_by_name_.end(), "Component's dependency points to a non-existent component");

const auto& dep_props = dep_it->second->component_properties;

Dependency dep{};
dep.process_state_ = score::mw::lifecycle::ProcessState::kRunning;

auto dep_it = component_by_name_.find(dep_name);
if (dep_it != component_by_name_.end())
if (dep_props.ready_condition.has_value())
{
const auto& dep_props = dep_it->second->component_properties;
if (dep_props.ready_condition.has_value())
{
dep.process_state_ = dep_props.ready_condition->process_state == ProcessState::Running
? score::mw::lifecycle::ProcessState::kRunning
: score::mw::lifecycle::ProcessState::kTerminated;
}
std::visit(
[&dep](auto&& arg) {
using argT = std::decay_t<decltype(arg)>;

if constexpr (std::is_same_v<argT, ProcessState>)
{
dep.process_state_ = arg == ProcessState::Running
? score::mw::lifecycle::ProcessState::kRunning
: score::mw::lifecycle::ProcessState::kTerminated;
return;
}
else if constexpr (std::is_same_v<argT, FileState>)
{
SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("FileState is not yet supported");
return;
}
},
dep_props.ready_condition.value());
}

dep.target_process_id_ = IdentifierHash{dep_name};
Expand Down Expand Up @@ -255,11 +271,9 @@ void ConfigurationAdapter::resolveDependsOnEntry(
return;
}

bool found = false;
auto comp_it = component_to_process_index_.find(dep_name);
if (comp_it != component_to_process_index_.end())
{
found = true;
if (std::find(indexes.begin(), indexes.end(), comp_it->second) == indexes.end())
{
indexes.push_back(comp_it->second);
Expand All @@ -278,14 +292,11 @@ void ConfigurationAdapter::resolveDependsOnEntry(
auto dep_it = depends_on_by_name.find(dep_name);
if (dep_it != depends_on_by_name.end())
{
found = true;
for (const auto& sub_dep : *dep_it->second)
{
resolveDependsOnEntry(sub_dep, depends_on_by_name, indexes, visited);
}
}

assert(found && "depends_on references unknown component or run_target");
}

ProcessGroupState ConfigurationAdapter::buildProcessGroupState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,28 @@ class ConfigurationAdapter final
bool buildFromConfig(const Config& config);

OsProcess buildOsProcess(const ComponentConfig& comp, uint32_t process_index) const;

void fillStartupConfigFromDeployment(
const ComponentConfig& comp,
score::mw::lifecycle::internal::osal::OsalConfig& startup) const;

void fillStartupArguments(
const ComponentProperties& props,
score::mw::lifecycle::internal::osal::OsalConfig& startup) const;

size_t fillStartupEnvironment(
const DeploymentConfig& deploy,
score::mw::lifecycle::internal::osal::OsalConfig& startup) const;

void appendAliveInterfaceEnvironment(
const ComponentConfig& comp,
size_t& env_index,
score::mw::lifecycle::internal::osal::OsalConfig& startup) const;

PgManagerConfig buildPgManagerConfig(const ComponentConfig& comp) const;
DependencyList buildDependencyList(const ComponentProperties& props) const;

/// @brief Given a components properties, creates a list of dependencies.
[[nodiscard]] DependencyList buildDependencyList(const ComponentProperties& props) const;

std::vector<ProcessGroupState> buildProcessGroupStates(const Config& config) const;
ProcessGroupState buildProcessGroupState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,5 +571,117 @@ TEST(ConfigurationAdapterFallbackTest, FallbackRunTargetResolvesDependenciesRecu
adapter.deinitialize();
}

TEST(ConfigurationAdapterDependencyTest, DependencyOnNonExistentComponentIsIgnored)
{
RecordProperty("Description", "When a component depends on a non-existent component, the dependency is skipped.");
RecordProperty("TestType", "interface-test");
RecordProperty("DerivationTechnique", "explorative-testing");

ComponentConfig comp_a;
comp_a.name = "comp_a";
comp_a.component_properties.application_profile.application_type = ApplicationType::Native;
comp_a.component_properties.application_profile.is_self_terminating = false;
comp_a.component_properties.depends_on = {"non_existent_component", "also_missing"};
comp_a.deployment_config.bin_dir = "/opt";
comp_a.component_properties.binary_name = "comp_a";
comp_a.deployment_config.working_dir = "/tmp";
comp_a.deployment_config.sandbox.uid = 0;
comp_a.deployment_config.sandbox.gid = 0;
comp_a.deployment_config.sandbox.scheduling_policy = SCHED_OTHER;
comp_a.deployment_config.sandbox.scheduling_priority = 0;

std::vector<ComponentConfig> components;
components.push_back(std::move(comp_a));

RunTargetConfig startup;
startup.name = "Startup";
startup.depends_on = {"comp_a"};
startup.transition_timeout_ms = 5000;
startup.recovery_action.run_target = "fallback_run_target";

std::vector<RunTargetConfig> run_targets;
run_targets.push_back(std::move(startup));

FallbackRunTargetConfig fallback;
fallback.transition_timeout_ms = 1500;
AliveSupervisionConfig alive;
alive.evaluation_cycle_ms = 500;

auto config = ConfigBuilder{}
.setComponents(std::move(components))
.setRunTargets(std::move(run_targets))
.setInitialRunTarget("Startup")
.setFallbackRunTarget(std::move(fallback))
.setAliveSupervision(alive)
.build();

ConfigurationAdapter adapter;
EXPECT_DEATH(adapter.initialize(config), "Component's dependency.*");
}

TEST(ConfigurationAdapterReadyConditionTest, FileStateReadyConditionTriggersAssert)
{
RecordProperty("Description", "When a dependency target has FileState ready_condition, it triggers an assertion.");
RecordProperty("TestType", "interface-test");
RecordProperty("DerivationTechnique", "explorative-testing");

ComponentConfig comp_a;
comp_a.name = "comp_a";
comp_a.component_properties.application_profile.application_type = ApplicationType::Native;
comp_a.component_properties.application_profile.is_self_terminating = false;
FileState file_state{"/tmp/ready.txt", FileExistenceState::Exists, std::chrono::milliseconds{100}};
comp_a.component_properties.ready_condition = ReadyCondition{file_state};
comp_a.deployment_config.bin_dir = "/opt";
comp_a.component_properties.binary_name = "comp_a";
comp_a.deployment_config.working_dir = "/tmp";
comp_a.deployment_config.sandbox.uid = 0;
comp_a.deployment_config.sandbox.gid = 0;
comp_a.deployment_config.sandbox.scheduling_policy = SCHED_OTHER;
comp_a.deployment_config.sandbox.scheduling_priority = 0;

ComponentConfig comp_b;
comp_b.name = "comp_b";
comp_b.component_properties.application_profile.application_type = ApplicationType::Native;
comp_b.component_properties.application_profile.is_self_terminating = false;
comp_b.component_properties.ready_condition = ReadyCondition{ProcessState::Running};
comp_b.component_properties.depends_on = {"comp_a"};
comp_b.deployment_config.bin_dir = "/opt";
comp_b.component_properties.binary_name = "comp_b";
comp_b.deployment_config.working_dir = "/tmp";
comp_b.deployment_config.sandbox.uid = 0;
comp_b.deployment_config.sandbox.gid = 0;
comp_b.deployment_config.sandbox.scheduling_policy = SCHED_OTHER;
comp_b.deployment_config.sandbox.scheduling_priority = 0;

std::vector<ComponentConfig> components;
components.push_back(std::move(comp_a));
components.push_back(std::move(comp_b));

RunTargetConfig startup;
startup.name = "Startup";
startup.depends_on = {"comp_b"};
startup.transition_timeout_ms = 5000;
startup.recovery_action.run_target = "fallback_run_target";

std::vector<RunTargetConfig> run_targets;
run_targets.push_back(std::move(startup));

FallbackRunTargetConfig fallback;
fallback.transition_timeout_ms = 1500;
AliveSupervisionConfig alive;
alive.evaluation_cycle_ms = 500;

auto config = ConfigBuilder{}
.setComponents(std::move(components))
.setRunTargets(std::move(run_targets))
.setInitialRunTarget("Startup")
.setFallbackRunTarget(std::move(fallback))
.setAliveSupervision(alive)
.build();

ConfigurationAdapter adapter;
EXPECT_DEATH(adapter.initialize(config), "FileState.*");
}

} // namespace
} // namespace score::mw::lifecycle::internal::configuration
Loading
Loading