Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
#define COMPONENT_CONFIG_HPP

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

#include "score/mw/launch_manager/configuration/environment_config.hpp"
Expand Down Expand Up @@ -48,24 +50,34 @@ struct ApplicationProfile
std::optional<ComponentAliveSupervision> alive_supervision;
};

enum class ProcessState : uint8_t
enum class FileExistenceState : uint8_t
{
Running = 0,
Terminated = 1
Exists = 0,
NotExisting,
};

struct FileState
{
std::string file_path;
FileExistenceState state;
std::chrono::milliseconds polling_interval;
};

struct ReadyCondition
enum class ProcessState : std::uint8_t
{
ProcessState process_state{ProcessState::Running};
Running = 0,
Terminated = 1
};

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

struct ComponentProperties
{
std::string binary_name;
ApplicationProfile application_profile;
std::vector<std::string> depends_on;
std::vector<std::string> process_arguments;
std::optional<ReadyCondition> ready_condition;
ReadyCondition ready_condition;
};
struct Sandbox
{
Expand Down
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>

#include "score/mw/launch_manager/configuration/component_config.hpp"
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 @@ -31,10 +31,12 @@ namespace
namespace fb = score::mw::lifecycle::internal::configuration::fb;

using ::testing::Eq;
using ::testing::FieldsAre;
using ::testing::IsFalse;
using ::testing::IsNull;
using ::testing::IsTrue;
using ::testing::StrEq;
using ::testing::VariantWith;

const score::filesystem::Path kTestPath{"/tmp/test_config.bin"};

Expand Down Expand Up @@ -260,14 +262,58 @@ TEST_F(FlatbufferConfigLoaderTest, LoadSingleComponent)
EXPECT_THAT(comp.component_properties.depends_on[0], Eq("other_comp"));
ASSERT_THAT(comp.component_properties.process_arguments.size(), Eq(1U));
EXPECT_THAT(comp.component_properties.process_arguments[0], Eq("--verbose"));
ASSERT_THAT(comp.component_properties.ready_condition.has_value(), IsTrue());
EXPECT_THAT(comp.component_properties.ready_condition->process_state, Eq(ProcessState::Running));
EXPECT_THAT(comp.component_properties.ready_condition, VariantWith<ProcessState>(Eq(ProcessState::Running)));
EXPECT_THAT(comp.deployment_config.ready_timeout_ms, Eq(1500U));
EXPECT_THAT(comp.deployment_config.shutdown_timeout_ms, Eq(2500U));
EXPECT_THAT(comp.deployment_config.bin_dir, Eq("/opt/bin"));
EXPECT_THAT(comp.deployment_config.working_dir, Eq("/tmp"));
}

TEST_F(FlatbufferConfigLoaderTest, LoadSingleComponentWithFileState)
{
RecordProperty("Description", "Loads a component whose ready_condition includes a file_state.");

::flatbuffers::FlatBufferBuilder fbb;

auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, false /*is_self_terminating*/);
auto bin_name = fbb.CreateString("my_binary");
auto file_state =
fb::CreateFileStateDirect(fbb, "/tmp/ready", fb::FileExistenceState::Exists, 0.01 /*polling_interval*/);
auto ready_cond = fb::CreateReadyCondition(fbb, std::nullopt, file_state);
auto comp_props = fb::CreateComponentProperties(
fbb, bin_name, app_profile, 0 /*depends_on*/, 0 /*process_arguments*/, ready_cond);

auto bin_dir = fbb.CreateString("/opt/bin");
auto work_dir = fbb.CreateString("/tmp");
auto sandbox = buildDefaultSandbox(fbb);
auto deploy = fb::CreateDeploymentConfig(
fbb,
1.5 /*ready_timeout*/,
2.5 /*shutdown_timeout*/,
0 /*environmental_variables*/,
bin_dir,
work_dir,
0 /*ready_recovery_action*/,
0 /*recovery_action*/,
sandbox);

auto comp_name = fbb.CreateString("TestComponent");
auto comp_desc = fbb.CreateString("A test component");
auto component = fb::CreateComponent(fbb, comp_name, comp_desc, comp_props, deploy);
auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset<fb::Component>>{component});

auto result = loadBuffer(buildConfigWithComponents(fbb, comps));

ASSERT_THAT(result.has_value(), IsTrue());
ASSERT_THAT(result->components().size(), Eq(1U));

const auto& comp = result->components()[0];
EXPECT_THAT(
comp.component_properties.ready_condition,
VariantWith<FileState>(
FieldsAre(Eq("/tmp/ready"), Eq(FileExistenceState::Exists), Eq(std::chrono::milliseconds{10}))));
}

TEST_F(FlatbufferConfigLoaderTest, LoadRunTargets)
{
RecordProperty("Description", "Loads run targets with dependencies and transition timeout.");
Expand Down Expand Up @@ -620,7 +666,8 @@ TEST_F(FlatbufferConfigLoaderTest, OptionalWatchdogAbsent)

TEST_F(FlatbufferConfigLoaderTest, OptionalReadyConditionAbsent)
{
RecordProperty("Description", "When no ready_condition is present on a component, it is nullopt.");
RecordProperty(
"Description", "When no ready_condition is present on a component, it defaults to ProcessState::Running.");

::flatbuffers::FlatBufferBuilder fbb;

Expand All @@ -634,7 +681,9 @@ TEST_F(FlatbufferConfigLoaderTest, OptionalReadyConditionAbsent)
auto result = loadBuffer(buildConfigWithComponents(fbb, comps));

ASSERT_THAT(result.has_value(), IsTrue());
EXPECT_THAT(result->components()[0].component_properties.ready_condition.has_value(), IsFalse());
EXPECT_THAT(
result->components()[0].component_properties.ready_condition,
VariantWith<ProcessState>(Eq(ProcessState::Running)));
}

// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ ProcessState convertProcessState(fb::ProcessState fb_state)
}
}

FileExistenceState convertFileExistenceState(fb::FileExistenceState fb_state)
{
switch (fb_state)
{
case fb::FileExistenceState::NotExisting:
return FileExistenceState::NotExisting;
case fb::FileExistenceState::Exists:
return FileExistenceState::Exists;
}
SCORE_LANGUAGE_FUTURECPP_UNREACHABLE();
}

score::cpp::expected<int32_t, IConfigLoader::Error> convertSchedulingPolicy(fb::SchedulingPolicy policy)
{
switch (policy)
Expand Down Expand Up @@ -296,19 +308,59 @@ score::cpp::expected<ApplicationProfile, IConfigLoader::Error> convertApplicatio
return result;
}

score::cpp::expected<FileState, IConfigLoader::Error> convertFileState(const fb::FileState& fb_fs)
{
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(
fb_fs.file_path(), "FileState::file_path must never be nullptr as it is required in the schema");
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(
fb_fs.polling_interval() != 0.0,
"No FileState::polling_interval is configured, this should have been defaulted with the script.");

auto polling_interval_ms = secondsToMs(fb_fs.polling_interval());
if (!polling_interval_ms.has_value())
{
LM_LOG_ERROR() << "Invalid value for FileState::polling_interval";
return score::cpp::make_unexpected(polling_interval_ms.error());
}
return FileState{
fb_fs.file_path()->str(),
convertFileExistenceState(fb_fs.state()),
std::chrono::milliseconds{*polling_interval_ms}};
}

score::cpp::expected<ReadyCondition, IConfigLoader::Error> convertReadyCondition(const fb::ReadyCondition* fb_rc)
{
ReadyCondition result{};
if (fb_rc != nullptr)
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(
fb_rc != nullptr, "No ReadyCondition is configured, this should have been defaulted with the script.");

const bool has_process_state = fb_rc->process_state().has_value();
const bool has_file_state = fb_rc->file_state() != nullptr;

if (has_process_state && has_file_state)
{
LM_LOG_ERROR() << "ReadyCondition cannot have both process_state and file_state set";
return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat);
}

if (has_process_state)
{
auto process_state = requireScalarValue(fb_rc->process_state(), "ReadyCondition::process_state");
if (!process_state.has_value())
return ReadyCondition{convertProcessState(*fb_rc->process_state())};
}

if (has_file_state)
{
auto file_state = convertFileState(*(fb_rc->file_state()));
if (!file_state.has_value())
{
return score::cpp::make_unexpected(process_state.error());
LM_LOG_ERROR() << "Invalid value for ReadyCondition::file_state";
return score::cpp::make_unexpected(file_state.error());
}
result.process_state = convertProcessState(*process_state);
}
return result;

// convertFileState only returns nullopt for a nullptr input, which is ruled out above
return ReadyCondition{*file_state};
};

SCORE_LANGUAGE_FUTURECPP_UNREACHABLE();
}

score::cpp::expected<ComponentProperties, IConfigLoader::Error> convertComponentProperties(
Expand All @@ -334,12 +386,12 @@ score::cpp::expected<ComponentProperties, IConfigLoader::Error> convertComponent
result.process_arguments = convertStringVector(fb_cp->process_arguments());
if (fb_cp->ready_condition() != nullptr)
{
auto ready_cond = convertReadyCondition(fb_cp->ready_condition());
if (!ready_cond.has_value())
auto ready_condition = convertReadyCondition(fb_cp->ready_condition());
if (!ready_condition.has_value())
{
return score::cpp::make_unexpected(ready_cond.error());
return score::cpp::make_unexpected(ready_condition.error());
}
result.ready_condition = std::move(*ready_cond);
result.ready_condition = std::move(ready_condition.value());
}
}
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ score::cpp::expected<TargetT, IConfigLoader::Error> validateRange(int64_t value,
[[nodiscard]] ApplicationType convertApplicationType(fb::ApplicationType fb_type);
/// @brief Converts a FlatBuffer ProcessState enum to the config ProcessState.
[[nodiscard]] ProcessState convertProcessState(fb::ProcessState fb_state);
/// @brief Converts a FlatBuffer FileState table to the config equivalent, or nullopt if absent.
[[nodiscard]] score::cpp::expected<FileState, IConfigLoader::Error> convertFileState(const fb::FileState& fb_fs);
/// @brief Converts a FlatBuffer FileExistenceState enum to the config equivalent.
[[nodiscard]] FileExistenceState convertFileExistenceState(fb::FileExistenceState fb_state);
/// @brief Converts a FlatBuffer SchedulingPolicy enum to a POSIX scheduling policy constant.
[[nodiscard]] score::cpp::expected<int32_t, IConfigLoader::Error> convertSchedulingPolicy(fb::SchedulingPolicy policy);

Expand Down Expand Up @@ -101,7 +105,7 @@ score::cpp::expected<TargetT, IConfigLoader::Error> validateRange(int64_t value,
/// @brief Converts a FlatBuffer ApplicationProfile to the config equivalent.
[[nodiscard]] score::cpp::expected<ApplicationProfile, IConfigLoader::Error> convertApplicationProfile(
const fb::ApplicationProfile* fb_ap);
/// @brief Converts a FlatBuffer ReadyCondition to the config equivalent.
/// @brief Converts a FlatBuffer ReadyCondition to the config equivalent, or nullopt if not configured.
[[nodiscard]] score::cpp::expected<ReadyCondition, IConfigLoader::Error> convertReadyCondition(
const fb::ReadyCondition* fb_rc);
/// @brief Converts a FlatBuffer ComponentProperties to the config equivalent.
Expand Down
Loading
Loading