diff --git a/MODULE.bazel b/MODULE.bazel index b6cd10238..1b7d4fb7b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -25,7 +25,7 @@ bazel_dep(name = "rules_oci", version = "2.3.0") bazel_dep(name = "aspect_rules_lint", version = "2.0.0") bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2") bazel_dep(name = "platforms", version = "1.0.0") -bazel_dep(name = "flatbuffers", version = "25.9.23") +bazel_dep(name = "flatbuffers", version = "25.12.19") bazel_dep(name = "download_utils", version = "1.2.2") bazel_dep(name = "googletest", version = "1.17.0.bcr.2") @@ -134,6 +134,13 @@ use_repo(oci, "debian-test-runtime", "debian-test-runtime_linux_amd64") bazel_dep(name = "score_baselibs_rust", version = "0.1.2") bazel_dep(name = "score_baselibs", version = "0.2.6") +# Temporarily overwrite baselibs to use the new flatbuffer config loader until there is a new release +git_override( + module_name = "score_baselibs", + commit = "498a4b256c9073602140243d30c33b106e279f75", + remote = "https://github.com/eclipse-score/baselibs.git", +) + # Hedron's Compile Commands Extractor for Bazel # https://github.com/hedronvision/bazel-compile-commands-extractor bazel_dep(name = "hedron_compile_commands", dev_dependency = True) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 6f3fcf108..3583d88fd 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -173,8 +173,9 @@ "https://bcr.bazel.build/modules/download_utils/1.2.2/source.json": "c88be2bc48c98371d35665b805f307a647c98c83327345c918d9088822d77928", "https://bcr.bazel.build/modules/envoy_api/0.0.0-20241214-918efc9/MODULE.bazel": "24e05f6f52f37be63a795192848555a2c8c855e7814dbc1ed419fb04a7005464", "https://bcr.bazel.build/modules/envoy_api/0.0.0-20241214-918efc9/source.json": "212043ab69d87f7a04aa4f627f725b540cff5e145a3a31a9403d8b6ec2e920c9", + "https://bcr.bazel.build/modules/flatbuffers/25.12.19/MODULE.bazel": "fe3a7f7811f43264f68136ad99e64384d70b2a25245e09ab800c4bb83171da25", + "https://bcr.bazel.build/modules/flatbuffers/25.12.19/source.json": "ea0204be7a79de9141cee5fa436e58a14e88b39b5b59227b21efa0394474ebea", "https://bcr.bazel.build/modules/flatbuffers/25.9.23/MODULE.bazel": "32753ba60bf3bacfe7737c0f3e8e3e55624b19af5d398c485580d57492d145d8", - "https://bcr.bazel.build/modules/flatbuffers/25.9.23/source.json": "a2116f0017f6896353fd3abf65ef2b89b0a257e8a87f395c5000f53934829f31", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", "https://bcr.bazel.build/modules/gazelle/0.27.0/MODULE.bazel": "3446abd608295de6d90b4a8a118ed64a9ce11dcb3dda2dc3290a22056bd20996", @@ -227,6 +228,8 @@ "https://bcr.bazel.build/modules/opencensus-cpp/0.0.0-20230502-50eb5de/source.json": "f50efc07822f5425bd1d3e40e977484f9c0142463052717d40ec85cd6744243e", "https://bcr.bazel.build/modules/opencensus-proto/0.4.1/MODULE.bazel": "4a2e8b4d0b544002502474d611a5a183aa282251e14f6a01afe841c0c1b10372", "https://bcr.bazel.build/modules/opencensus-proto/0.4.1/source.json": "a7d956700a85b833c43fc61455c0e111ab75bab40768ed17a206ee18a2bbe38f", + "https://bcr.bazel.build/modules/openssl/3.5.5.bcr.4/MODULE.bazel": "b3f35b53c6c73bd3a7c8efbf9b7e79e92566d189e64d313aede69f608ac6dd77", + "https://bcr.bazel.build/modules/openssl/3.5.5.bcr.4/source.json": "662d68be9227e60ef65fed83c6820564bda4af0efe6a40dc62dd9aee288fe9cf", "https://bcr.bazel.build/modules/opentelemetry-cpp/1.14.2/MODULE.bazel": "089a5613c2a159c7dfde098dabfc61e966889c7d6a81a98422a84c51535ed17d", "https://bcr.bazel.build/modules/opentelemetry-cpp/1.16.0/MODULE.bazel": "b7379a140f538cea3f749179a2d481ed81942cc6f7b05a6113723eb34ac3b3e7", "https://bcr.bazel.build/modules/opentelemetry-cpp/1.16.0/source.json": "da0cf667713b1e48d7f8912b100b4e0a8284c8a95717af5eb8c830d699e61cf5", @@ -309,7 +312,6 @@ "https://bcr.bazel.build/modules/rules_cc/0.1.4/MODULE.bazel": "bb03a452a7527ac25a7518fb86a946ef63df860b9657d8323a0c50f8504fb0b9", "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", - "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", @@ -384,6 +386,8 @@ "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/source.json": "6a6ca0940914d55c550d1417cad13a56c9900e23f651a762d8ccc5a64adcf661", "https://bcr.bazel.build/modules/rules_oci/2.3.0/MODULE.bazel": "49075197960c924c0a4d759b7c765c3d00a41d2fdd4a943b42823c1d016ab4ec", "https://bcr.bazel.build/modules/rules_oci/2.3.0/source.json": "47710c28446211b5e61a24015a4669c50c6862d5f91e6bdbc710de8d750cf613", + "https://bcr.bazel.build/modules/rules_perl/1.1.0/MODULE.bazel": "22138e75bb8f1ee6c21f609b90d2c24b0c9b796ccf55cc04c1c9190b699f7e9d", + "https://bcr.bazel.build/modules/rules_perl/1.1.0/source.json": "896fe7707a38c5b229c6f5fa77134209874c4d57fecda5f756c1f23e4d25aae2", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.1.0/MODULE.bazel": "9db8031e71b6ef32d1846106e10dd0ee2deac042bd9a2de22b4761b0c3036453", @@ -599,6 +603,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/download_utils/1.0.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/download_utils/1.2.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/envoy_api/0.0.0-20241214-918efc9/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/flatbuffers/25.12.19/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/flatbuffers/25.9.23/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.27.0/MODULE.bazel": "not found", @@ -637,6 +642,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/nlohmann_json/3.6.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opencensus-cpp/0.0.0-20230502-50eb5de/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opencensus-proto/0.4.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/openssl/3.5.5.bcr.4/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opentelemetry-cpp/1.14.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opentelemetry-cpp/1.16.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opentelemetry-proto/1.1.0/MODULE.bazel": "not found", @@ -705,7 +711,6 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.1.4/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.1.5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.14/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.16/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.17/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.4/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.8/MODULE.bazel": "not found", @@ -768,6 +773,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.3.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.5.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_oci/2.3.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_perl/1.1.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/0.7.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/1.0.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/1.1.0/MODULE.bazel": "not found", @@ -814,8 +820,6 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_swift/1.16.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_swift/1.18.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_swift/2.1.1/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_baselibs/0.2.6/MODULE.bazel": "36db2ffb07d1b4cc91f44ffbdb17cab834baef0fb3173c5e9b6885868fdc420c", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_baselibs/0.2.6/source.json": "a8bb7e82abe9d4de6301e4a28252e51323d4a758fe0b8e74ac9fd8c3ea625c3f", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_baselibs_rust/0.1.2/MODULE.bazel": "8a07e1c62986e941a6e182e39c9cd08391849eed55c2f4dbb02d72ef6f524346", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_baselibs_rust/0.1.2/source.json": "5d920b55b98cf3d87da75d5a3ab1915709867fda14991ef2843c5e7c33aa0391", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_cpp_toolchains/0.2.2/MODULE.bazel": "343a1892b1d5c616e0b4cbecfb5e548fa69328d22bb4fd5862bdd3cfa902142b", @@ -1343,40 +1347,6 @@ ] } }, - "@@aspect_rules_ts+//ts:extensions.bzl%ext": { - "general": { - "bzlTransitiveDigest": "MTYrHsOSgiQcK2kWLEJXgQN+rPXhhAOe+aJ7E65JPKE=", - "usagesDigest": "caXVbnxEUN71ZxydJbg6pZ8NaFPVDbNp12wS9TMC824=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "npm_typescript": { - "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", - "attributes": { - "bzlmod": true, - "version": "5.8.3", - "integrity": "", - "build_file": "@@aspect_rules_ts+//ts:BUILD.typescript", - "build_file_substitutions": { - "bazel_worker_version": "5.4.2", - "google_protobuf_version": "3.20.1" - }, - "urls": [ - "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "aspect_rules_ts+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { "general": { "bzlTransitiveDigest": "gA7tPEdJXhskzPIEUxjX9IdDrM6+WjfbgXJ8Ez47umk=", @@ -2289,7 +2259,7 @@ "@@rules_nodejs+//nodejs:extensions.bzl%node": { "general": { "bzlTransitiveDigest": "FmfMiNXAxRoLWw3NloQbssosE1egrSvzirbQnso7j7E=", - "usagesDigest": "xQt2gTXxeHPqo+xDPEb0l8bxaCWaMbfvKRJcHvysWMI=", + "usagesDigest": "RmjWGl+udnOtLbA4SVIYVixOYhc+YyN36MKAPAhLDWw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/src/launch_manager_daemon/config/BUILD b/src/launch_manager_daemon/config/BUILD index 67f38aa85..b1b177628 100644 --- a/src/launch_manager_daemon/config/BUILD +++ b/src/launch_manager_daemon/config/BUILD @@ -10,7 +10,84 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* + +load("@score_baselibs//score/flatbuffers/bazel:codegen.bzl", "generate_cpp") + exports_files([ "lm_flatcfg.fbs", "lm_flatcfg_generated.h", ]) + +filegroup( + name = "new_lm_flatcfg_fbs", + srcs = ["src/new_lm_flatcfg.fbs"], + visibility = ["//visibility:public"], +) + +generate_cpp( + name = "new_lm_flatcfg_generated", + output = "new_lm_flatcfg_generated.h", + schema = "src/new_lm_flatcfg.fbs", +) + +cc_library( + name = "config", + srcs = ["src/config.cpp"], + hdrs = ["include/config.hpp"], + includes = ["include"], + visibility = ["//src:__subpackages__"], +) + +cc_library( + name = "config_loader", + hdrs = ["include/config_loader.hpp"], + includes = ["include"], + visibility = ["//src:__subpackages__"], + deps = [ + ":config", + "@score_baselibs//score/filesystem", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "flatbuffer_config_loader", + srcs = [ + "src/flatbuffer_config_loader.cpp", + ":new_lm_flatcfg_generated", + ], + hdrs = ["include/flatbuffer_config_loader.hpp"], + includes = [ + ".", + "include", + "src", + ], + visibility = ["//src:__subpackages__"], + deps = [ + ":config_loader", + "//src/launch_manager_daemon/common:log", + "@flatbuffers", + "@score_baselibs//score/flatbuffers:flatbufferutils", + ], +) + +cc_test( + name = "config_UT", + srcs = ["src/config_UT.cpp"], + visibility = ["//tests:__subpackages__"], + deps = [ + ":config", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "flatbuffer_config_loader_UT", + srcs = ["src/flatbuffer_config_loader_UT.cpp"], + visibility = ["//tests:__subpackages__"], + deps = [ + ":flatbuffer_config_loader", + "@flatbuffers", + "@googletest//:gtest_main", + ], +) diff --git a/src/launch_manager_daemon/config/include/config.hpp b/src/launch_manager_daemon/config/include/config.hpp new file mode 100644 index 000000000..c15ec6aec --- /dev/null +++ b/src/launch_manager_daemon/config/include/config.hpp @@ -0,0 +1,261 @@ +/******************************************************************************** + * 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 CONFIG_HPP +#define CONFIG_HPP + +#include +#include +#include +#include +#include +#include + +namespace score::launch_manager::config +{ + +enum class ApplicationType : uint8_t +{ + Native = 0, + Reporting = 1, + ReportingAndSupervised = 2, + StateManager = 3 +}; + +enum class ProcessState : uint8_t +{ + Running = 0, + Terminated = 1 +}; + +struct ComponentAliveSupervision +{ + uint32_t reporting_cycle_ms{}; + uint32_t failed_cycles_tolerance{}; + std::optional min_indications; + std::optional max_indications; +}; + +struct ApplicationProfile +{ + ApplicationType application_type{ApplicationType::Native}; + bool is_self_terminating{}; + std::optional alive_supervision; +}; + +struct ReadyCondition +{ + ProcessState process_state{ProcessState::Running}; +}; + +struct ComponentProperties +{ + std::string binary_name; + ApplicationProfile application_profile; + std::vector depends_on; + std::vector process_arguments; + std::optional ready_condition; +}; + +class EnvironmentVariable +{ + public: + EnvironmentVariable(std::string_view key, std::string_view value); + + std::string_view key() const; + std::string_view value() const; + const char* c_str() const; + + private: + std::string entry_; + std::size_t key_length_; +}; + +class Environment +{ + public: + using const_iterator = std::vector::const_iterator; + + Environment() = default; + + Environment(const Environment&) = delete; + Environment& operator=(const Environment&) = delete; + Environment(Environment&& other) noexcept; + Environment& operator=(Environment&& other) noexcept; + + ~Environment() = default; + + void reserve(std::size_t count); + void add(std::string_view key, std::string_view value); + + const_iterator begin() const; + const_iterator end() const; + std::size_t size() const; + + char* const* envp() const; + + private: + void rebuildPointers() const; + std::vector entries_; + mutable std::vector pointers_; +}; + +struct RestartAction +{ + uint32_t number_of_attempts{}; + uint32_t delay_before_restart_ms{}; +}; + +struct SwitchRunTargetAction +{ + std::string run_target; +}; + +struct Sandbox +{ + uid_t uid{}; + gid_t gid{}; + std::vector supplementary_group_ids; + std::optional security_policy; + int32_t scheduling_policy; + int32_t scheduling_priority{}; + std::optional max_memory_usage; + std::optional max_cpu_usage; +}; + +struct DeploymentConfig +{ + uint32_t ready_timeout_ms{}; + uint32_t shutdown_timeout_ms{}; + Environment environmental_variables; + std::string bin_dir; + std::string working_dir; + std::optional ready_recovery_action; + // Currently only SwitchRunTargetAction is supported here, RestartAction to be added in the future + std::optional recovery_action; + Sandbox sandbox; +}; + +struct ComponentConfig +{ + std::string name; + std::string description; + ComponentProperties component_properties; + DeploymentConfig deployment_config; +}; + +struct RunTargetConfig +{ + std::string name; + std::string description; + std::vector depends_on; + uint32_t transition_timeout_ms{}; + SwitchRunTargetAction recovery_action; +}; + +struct FallbackRunTargetConfig +{ + std::string description; + std::vector depends_on; + uint32_t transition_timeout_ms{}; +}; + +struct AliveSupervisionConfig +{ + uint32_t evaluation_cycle_ms{}; +}; + +struct WatchdogConfig +{ + std::string device_file_path; + uint32_t max_timeout_ms{}; + bool deactivate_on_shutdown{}; + bool require_magic_close{}; +}; + +class ConfigBuilder; + +/// @brief Move-only value object holding the parsed launch-manager configuration. +/// +/// Loaded once by an IConfigLoader, then individual parts are moved out via +/// the `take*()` accessors to transfer ownership to dedicated domain objects +/// (e.g. ComponentConfig to Component). +/// +/// @note As of now, everything is expected in a single config file. +/// Even though some fields appear as optional in the json schema, they are only optional if the configuration would be +/// split across multiple files. As only a single file is supported, the single config will contain all fields. +class Config +{ + public: + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + Config(Config&&) = default; + Config& operator=(Config&&) = default; + + // Read access + const std::vector& components() const; + const std::vector& runTargets() const; + std::string_view initialRunTarget() const; + const FallbackRunTargetConfig& fallbackRunTarget() const; + const AliveSupervisionConfig& aliveSupervision() const; + const std::optional& watchdog() const; + + // Ownership transfer — source is left in a moved-from state after the call + std::vector takeComponents(); + std::vector takeRunTargets(); + std::string takeInitialRunTarget(); + FallbackRunTargetConfig takeFallbackRunTarget(); + AliveSupervisionConfig takeAliveSupervision(); + std::optional takeWatchdog(); + + private: + friend class ConfigBuilder; + + Config(std::vector components, + std::vector run_targets, + std::string initial_run_target, + FallbackRunTargetConfig fallback_run_target, + AliveSupervisionConfig alive_supervision, + std::optional watchdog); + + std::vector components_; + std::vector run_targets_; + std::string initial_run_target_; + FallbackRunTargetConfig fallback_run_target_; + AliveSupervisionConfig alive_supervision_; + std::optional watchdog_; +}; + +class ConfigBuilder +{ + public: + ConfigBuilder& setComponents(std::vector components); + ConfigBuilder& setRunTargets(std::vector run_targets); + ConfigBuilder& setInitialRunTarget(std::string initial_run_target); + ConfigBuilder& setFallbackRunTarget(FallbackRunTargetConfig fallback); + ConfigBuilder& setAliveSupervision(AliveSupervisionConfig alive_supervision); + ConfigBuilder& setWatchdog(WatchdogConfig watchdog); + + Config build(); + + private: + std::string initial_run_target_; + std::vector components_; + std::vector run_targets_; + FallbackRunTargetConfig fallback_run_target_; + AliveSupervisionConfig alive_supervision_; + std::optional watchdog_; +}; + +} // namespace score::launch_manager::config + +#endif // CONFIG_HPP diff --git a/src/launch_manager_daemon/config/include/config_loader.hpp b/src/launch_manager_daemon/config/include/config_loader.hpp new file mode 100644 index 000000000..6eafd58c5 --- /dev/null +++ b/src/launch_manager_daemon/config/include/config_loader.hpp @@ -0,0 +1,53 @@ +/******************************************************************************** + * 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 CONFIG_LOADER_HPP +#define CONFIG_LOADER_HPP + +#include "config.hpp" + +#include "score/filesystem/path.h" + +#include +#include + +namespace score::launch_manager::config +{ + +/// @brief Abstract interface for loading Launch Manager configuration from a file. +/// +/// Decouples the configuration consumer from the serialization format (e.g., JSON, FlatBuffers). +/// A concrete implementation parses the file and returns a format-independent @ref Config object. +class IConfigLoader +{ + public: + /// @brief Error codes returned when configuration loading fails. + enum class Error : std::uint32_t + { + FileNotFound, ///< The configuration file does not exist at the given path. + InsufficientPermission, ///< The process lacks read permissions for the file. + InvalidFormat, ///< The file contents could not be parsed (malformed or missing required fields). + UnsupportedVersion, ///< The file uses a configuration schema version not supported by this loader. + GeneralError ///< Any other failure not covered by the above codes. + }; + + virtual ~IConfigLoader() = default; + + /// @brief Loads and parses the configuration file at @p path. + /// @param path Filesystem path to the configuration file. + /// @return A @ref Config object on success, or an @ref Error on failure. + virtual score::cpp::expected load(const score::filesystem::Path& path) = 0; +}; + +} // namespace score::launch_manager::config + +#endif // CONFIG_LOADER_HPP diff --git a/src/launch_manager_daemon/config/include/flatbuffer_config_loader.hpp b/src/launch_manager_daemon/config/include/flatbuffer_config_loader.hpp new file mode 100644 index 000000000..f23dfc5e2 --- /dev/null +++ b/src/launch_manager_daemon/config/include/flatbuffer_config_loader.hpp @@ -0,0 +1,71 @@ +/******************************************************************************** + * 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 FLATBUFFER_CONFIG_LOADER_HPP +#define FLATBUFFER_CONFIG_LOADER_HPP + +#include "config_loader.hpp" + +#include "score/flatbuffers/load_buffer.hpp" + +#include +#include + +namespace score::launch_manager::config +{ + +/// @brief Internal helpers for FlatBuffer config parsing. +namespace details +{ + +score::cpp::expected parseFlatbuffer(const std::vector& buffer); +IConfigLoader::Error mapOsError(const score::os::Error& error); + +} // namespace details + +/// @brief Default buffer loader that delegates to score::flatbuffers::LoadBuffer. +/// +/// Exists as a named type so that FlatbufferConfigLoaderImpl can be parameterized on the +/// buffer-loading strategy. In production the default is used; in tests a mock +/// loader can be substituted without virtual dispatch overhead. +struct DefaultBufferLoader +{ + static score::os::Result> load(const score::filesystem::Path& path) + { + return score::flatbuffers::LoadBuffer(path); + } +}; + +/// @brief IConfigLoader implementation that reads a FlatBuffer binary file. +/// @tparam BufferLoaderT Policy type providing a static `load(path)` method. +/// Defaults to DefaultBufferLoader; replace with a mock for testing. +template +class FlatbufferConfigLoaderImpl : public IConfigLoader +{ + public: + score::cpp::expected load(const score::filesystem::Path& path) override + { + auto buffer_result = BufferLoaderT::load(path); + if (!buffer_result.has_value()) + { + return score::cpp::make_unexpected(details::mapOsError(buffer_result.error())); + } + return details::parseFlatbuffer(buffer_result.value()); + } +}; + +/// @brief Production-ready alias using the default buffer loader. +using FlatbufferConfigLoader = FlatbufferConfigLoaderImpl<>; + +} // namespace score::launch_manager::config + +#endif // FLATBUFFER_CONFIG_LOADER_HPP diff --git a/src/launch_manager_daemon/config/lm_flatcfg_generated.h b/src/launch_manager_daemon/config/lm_flatcfg_generated.h index f82a2547f..83ed21465 100644 --- a/src/launch_manager_daemon/config/lm_flatcfg_generated.h +++ b/src/launch_manager_daemon/config/lm_flatcfg_generated.h @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2025 Contributors to the Eclipse Foundation + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -21,8 +21,8 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && - FLATBUFFERS_VERSION_MINOR == 9 && - FLATBUFFERS_VERSION_REVISION == 23, + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, "Non-compatible flatbuffers version included"); namespace LMFlatBuffer { @@ -137,7 +137,8 @@ struct LMEcuCfg FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::Vector<::flatbuffers::Offset> *Process() const { return GetPointer> *>(VT_PROCESS); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_VERSIONMAJOR, 4) && VerifyField(verifier, VT_VERSIONMINOR, 4) && @@ -232,7 +233,8 @@ struct ModeGroup FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::Vector<::flatbuffers::Offset> *modeDeclaration() const { return GetPointer> *>(VT_MODEDECLARATION); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_IDENTIFIER) && verifier.VerifyString(identifier()) && @@ -324,7 +326,8 @@ struct ModeDeclaration FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::String *identifier() const { return GetPointer(VT_IDENTIFIER); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_IDENTIFIER) && verifier.VerifyString(identifier()) && @@ -415,7 +418,8 @@ struct Process FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::Vector<::flatbuffers::Offset> *sgids() const { return GetPointer> *>(VT_SGIDS); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_IDENTIFIER) && verifier.VerifyString(identifier()) && @@ -604,7 +608,8 @@ struct ProcessStartupConfig FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Tab uint64_t memoryUsage() const { return GetField(VT_MEMORYUSAGE, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_IDENTIFIER) && verifier.VerifyString(identifier()) && @@ -765,7 +770,8 @@ struct ProcessGroupStateDependency FLATBUFFERS_FINAL_CLASS : private ::flatbuffe const ::flatbuffers::String *stateName() const { return GetPointer(VT_STATENAME); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_STATEMACHINE_NAME) && verifier.VerifyString(stateMachine_name()) && @@ -830,7 +836,8 @@ struct ProcessExecutionDependency FLATBUFFERS_FINAL_CLASS : private ::flatbuffer const ::flatbuffers::String *targetProcess_identifier() const { return GetPointer(VT_TARGETPROCESS_IDENTIFIER); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_STATENAME) && verifier.VerifyString(stateName()) && @@ -895,7 +902,8 @@ struct EnvironmentVariable FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Tabl const ::flatbuffers::String *value() const { return GetPointer(VT_VALUE); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_KEY) && verifier.VerifyString(key()) && @@ -956,7 +964,8 @@ struct ProcessArgument FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::String *argument() const { return GetPointer(VT_ARGUMENT); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_ARGUMENT) && verifier.VerifyString(argument()) && @@ -1007,7 +1016,8 @@ struct ProcessSgid FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint32_t sgid() const { return GetField(VT_SGID, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_SGID, 4) && verifier.EndTable(); @@ -1062,14 +1072,16 @@ inline bool SizePrefixedLMEcuCfgBufferHasIdentifier(const void *buf) { buf, LMEcuCfgIdentifier(), true); } +template inline bool VerifyLMEcuCfgBuffer( - ::flatbuffers::Verifier &verifier) { - return verifier.VerifyBuffer(LMEcuCfgIdentifier()); + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(LMEcuCfgIdentifier()); } +template inline bool VerifySizePrefixedLMEcuCfgBuffer( - ::flatbuffers::Verifier &verifier) { - return verifier.VerifySizePrefixedBuffer(LMEcuCfgIdentifier()); + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(LMEcuCfgIdentifier()); } inline const char *LMEcuCfgExtension() { diff --git a/src/launch_manager_daemon/config/src/config.cpp b/src/launch_manager_daemon/config/src/config.cpp new file mode 100644 index 000000000..90139ea28 --- /dev/null +++ b/src/launch_manager_daemon/config/src/config.cpp @@ -0,0 +1,231 @@ +/******************************************************************************** + * 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 "config.hpp" +#include + +#include + +namespace score::launch_manager::config +{ + +// --- EnvironmentVariable --- + +EnvironmentVariable::EnvironmentVariable(std::string_view key, std::string_view value) + : entry_{std::string{key} + "=" + std::string{value}}, key_length_{key.size()} +{ + assert(!key.empty() && "Environment variable key must not be empty"); +} + +std::string_view EnvironmentVariable::key() const +{ + return {entry_.data(), key_length_}; +} + +std::string_view EnvironmentVariable::value() const +{ + return {entry_.data() + key_length_ + 1, entry_.size() - key_length_ - 1}; +} + +const char* EnvironmentVariable::c_str() const +{ + return entry_.c_str(); +} + +// --- Environment --- + +Environment::Environment(Environment&& other) noexcept + : entries_{std::move(other.entries_)} +{ + rebuildPointers(); +} + +Environment& Environment::operator=(Environment&& other) noexcept +{ + if (this != &other) + { + entries_ = std::move(other.entries_); + rebuildPointers(); + } + return *this; +} + +void Environment::reserve(std::size_t count) +{ + entries_.reserve(count); + // +1 for nullptr terminator + pointers_.reserve(count + 1); +} + +void Environment::add(std::string_view key, std::string_view value) +{ + entries_.emplace_back(key, value); +} + +Environment::const_iterator Environment::begin() const +{ + return entries_.begin(); +} + +Environment::const_iterator Environment::end() const +{ + return entries_.end(); +} + +std::size_t Environment::size() const +{ + return entries_.size(); +} + +char* const* Environment::envp() const +{ + rebuildPointers(); + // const_cast is required to convert to the type expected by execve + return const_cast(pointers_.data()); +} + +void Environment::rebuildPointers() const +{ + pointers_.clear(); + // +1 for nullptr terminator + pointers_.reserve(entries_.size() + 1); + for (const auto& e : entries_) + { + pointers_.push_back(e.c_str()); + } + pointers_.push_back(nullptr); +} + +// --- Config --- + +Config::Config(std::vector components, + std::vector run_targets, + std::string initial_run_target, + FallbackRunTargetConfig fallback_run_target, + AliveSupervisionConfig alive_supervision, + std::optional watchdog) + : components_{std::move(components)}, + run_targets_{std::move(run_targets)}, + initial_run_target_{std::move(initial_run_target)}, + fallback_run_target_{std::move(fallback_run_target)}, + alive_supervision_{std::move(alive_supervision)}, + watchdog_{std::move(watchdog)} +{ +} + +ConfigBuilder& ConfigBuilder::setComponents(std::vector components) +{ + components_ = std::move(components); + return *this; +} + +ConfigBuilder& ConfigBuilder::setRunTargets(std::vector run_targets) +{ + run_targets_ = std::move(run_targets); + return *this; +} + +ConfigBuilder& ConfigBuilder::setInitialRunTarget(std::string initial_run_target) +{ + initial_run_target_ = std::move(initial_run_target); + return *this; +} + +ConfigBuilder& ConfigBuilder::setFallbackRunTarget(FallbackRunTargetConfig fallback) +{ + fallback_run_target_ = std::move(fallback); + return *this; +} + +ConfigBuilder& ConfigBuilder::setAliveSupervision(AliveSupervisionConfig alive_supervision) +{ + alive_supervision_ = std::move(alive_supervision); + return *this; +} + +ConfigBuilder& ConfigBuilder::setWatchdog(WatchdogConfig watchdog) +{ + watchdog_ = std::move(watchdog); + return *this; +} + +Config ConfigBuilder::build() +{ + return Config(std::move(components_), + std::move(run_targets_), + std::move(initial_run_target_), + std::move(fallback_run_target_), + std::move(alive_supervision_), + std::move(watchdog_)); +} + +const std::vector& Config::components() const +{ + return components_; +} + +const std::vector& Config::runTargets() const +{ + return run_targets_; +} + +std::string_view Config::initialRunTarget() const +{ + return initial_run_target_; +} + +const FallbackRunTargetConfig& Config::fallbackRunTarget() const +{ + return fallback_run_target_; +} + +const AliveSupervisionConfig& Config::aliveSupervision() const +{ + return alive_supervision_; +} + +const std::optional& Config::watchdog() const +{ + return watchdog_; +} + +std::vector Config::takeComponents() +{ + return std::move(components_); +} + +std::vector Config::takeRunTargets() +{ + return std::move(run_targets_); +} + +std::string Config::takeInitialRunTarget() +{ + return std::move(initial_run_target_); +} + +FallbackRunTargetConfig Config::takeFallbackRunTarget() +{ + return std::move(fallback_run_target_); +} + +AliveSupervisionConfig Config::takeAliveSupervision() +{ + return std::move(alive_supervision_); +} + +std::optional Config::takeWatchdog() +{ + return std::move(watchdog_); +} + +} // namespace score::launch_manager::config diff --git a/src/launch_manager_daemon/config/src/config_UT.cpp b/src/launch_manager_daemon/config/src/config_UT.cpp new file mode 100644 index 000000000..1f56de344 --- /dev/null +++ b/src/launch_manager_daemon/config/src/config_UT.cpp @@ -0,0 +1,260 @@ +/******************************************************************************** + * 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 "config.hpp" + +#include +#include + +#include +#include +#include + +namespace score::launch_manager::config +{ +namespace +{ + +using ::testing::Eq; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::StrEq; + +class EnvironmentVariableTest : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + } +}; + +TEST_F(EnvironmentVariableTest, KeyAndValueAreAccessible) +{ + RecordProperty("Description", "Key and value are correctly split from the internal key=value storage."); + + EnvironmentVariable ev{"PATH", "/usr/bin"}; + + EXPECT_THAT(ev.key(), Eq("PATH")); + EXPECT_THAT(ev.value(), Eq("/usr/bin")); +} + +TEST_F(EnvironmentVariableTest, CStrReturnsKeyEqualsValue) +{ + RecordProperty("Description", "c_str() returns the full key=value string."); + + EnvironmentVariable ev{"HOME", "/root"}; + + EXPECT_THAT(ev.c_str(), StrEq("HOME=/root")); +} + +TEST_F(EnvironmentVariableTest, EmptyValue) +{ + RecordProperty("Description", "Empty value is handled correctly."); + + EnvironmentVariable ev{"KEY", ""}; + + EXPECT_THAT(ev.key(), Eq("KEY")); + EXPECT_THAT(ev.value(), Eq("")); + EXPECT_THAT(ev.c_str(), StrEq("KEY=")); +} + +TEST_F(EnvironmentVariableTest, ValueContainingEquals) +{ + RecordProperty("Description", "A value containing '=' is preserved correctly."); + + EnvironmentVariable ev{"OPTS", "a=1,b=2"}; + + EXPECT_THAT(ev.key(), Eq("OPTS")); + EXPECT_THAT(ev.value(), Eq("a=1,b=2")); + EXPECT_THAT(ev.c_str(), StrEq("OPTS=a=1,b=2")); +} + +class EnvironmentTest : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + } +}; + +TEST_F(EnvironmentTest, DefaultConstructedIsEmpty) +{ + RecordProperty("Description", "A default-constructed Environment has size 0 and begin == end."); + + Environment env; + + EXPECT_THAT(env.size(), Eq(0U)); + EXPECT_THAT(env.begin(), Eq(env.end())); +} + +TEST_F(EnvironmentTest, AddIncreasesSize) +{ + RecordProperty("Description", "Adding entries increases size and they are iterable."); + + Environment env; + env.add("A", "1"); + env.add("B", "2"); + + EXPECT_THAT(env.size(), Eq(2U)); + auto it = env.begin(); + EXPECT_THAT(it->key(), Eq("A")); + EXPECT_THAT(it->value(), Eq("1")); + ++it; + EXPECT_THAT(it->key(), Eq("B")); + EXPECT_THAT(it->value(), Eq("2")); + ++it; + EXPECT_THAT(it, Eq(env.end())); +} + +TEST_F(EnvironmentTest, EnvpReturnsNullTerminatedArray) +{ + RecordProperty("Description", "envp() returns a null-terminated array suitable for execve."); + + Environment env; + env.add("KEY", "val"); + + char* const* envp = env.envp(); + + ASSERT_THAT(envp, NotNull()); + EXPECT_THAT(envp[0], StrEq("KEY=val")); + EXPECT_THAT(envp[1], IsNull()); +} + +TEST_F(EnvironmentTest, EnvpWithMultipleEntries) +{ + RecordProperty("Description", "envp() contains all entries in order followed by nullptr."); + + Environment env; + env.add("A", "1"); + env.add("B", "2"); + env.add("C", "3"); + + char* const* envp = env.envp(); + + EXPECT_THAT(envp[0], StrEq("A=1")); + EXPECT_THAT(envp[1], StrEq("B=2")); + EXPECT_THAT(envp[2], StrEq("C=3")); + EXPECT_THAT(envp[3], IsNull()); +} + +TEST_F(EnvironmentTest, ReserveAndAddAvoidReallocation) +{ + RecordProperty("Description", "reserve() followed by add() produces correct entries and envp."); + + Environment env; + env.reserve(3); + env.add("X", "10"); + env.add("Y", "20"); + env.add("Z", "30"); + + EXPECT_THAT(env.size(), Eq(3U)); + + char* const* envp = env.envp(); + EXPECT_THAT(envp[0], StrEq("X=10")); + EXPECT_THAT(envp[1], StrEq("Y=20")); + EXPECT_THAT(envp[2], StrEq("Z=30")); + EXPECT_THAT(envp[3], IsNull()); +} + +TEST_F(EnvironmentTest, MoveConstructorPreservesEntries) +{ + RecordProperty("Description", "Move-constructed Environment has the original entries and valid envp."); + + Environment original; + original.add("FOO", "bar"); + original.add("BAZ", "qux"); + + Environment moved{std::move(original)}; + + EXPECT_THAT(moved.size(), Eq(2U)); + auto it = moved.begin(); + EXPECT_THAT(it->key(), Eq("FOO")); + EXPECT_THAT(it->value(), Eq("bar")); + ++it; + EXPECT_THAT(it->key(), Eq("BAZ")); + EXPECT_THAT(it->value(), Eq("qux")); + + char* const* envp = moved.envp(); + EXPECT_THAT(envp[0], StrEq("FOO=bar")); + EXPECT_THAT(envp[1], StrEq("BAZ=qux")); + EXPECT_THAT(envp[2], IsNull()); +} + +TEST_F(EnvironmentTest, MoveAssignmentPreservesEntries) +{ + RecordProperty("Description", "Move-assigned Environment has the original entries and valid envp."); + + Environment original; + original.add("K", "V"); + + Environment target; + target = std::move(original); + + EXPECT_THAT(target.size(), Eq(1U)); + EXPECT_THAT(target.begin()->key(), Eq("K")); + + char* const* envp = target.envp(); + EXPECT_THAT(envp[0], StrEq("K=V")); + EXPECT_THAT(envp[1], IsNull()); +} + +TEST_F(EnvironmentTest, SsoLengthStringSurvivesReallocation) +{ + RecordProperty("Description", "Short strings within SSO threshold have valid envp after reallocation."); + + Environment env; + for (int i = 0; i < 100; ++i) + { + env.add("K", std::to_string(i)); + } + + char* const* envp = env.envp(); + EXPECT_THAT(envp[0], StrEq("K=0")); + EXPECT_THAT(envp[99], StrEq("K=99")); + EXPECT_THAT(envp[100], IsNull()); +} + +TEST_F(EnvironmentTest, EmptyEnvironmentEnvpIsNullTerminated) +{ + RecordProperty("Description", "envp() on an empty Environment returns a null-terminated array."); + + Environment env; + + char* const* envp = env.envp(); + + ASSERT_THAT(envp, NotNull()); + EXPECT_THAT(envp[0], IsNull()); +} + +TEST_F(EnvironmentTest, RangeBasedForLoopWorks) +{ + RecordProperty("Description", "Environment supports range-based for loop."); + + Environment env; + env.add("A", "1"); + env.add("B", "2"); + + std::size_t count = 0; + for (const auto& ev : env) + { + EXPECT_THAT(ev.c_str(), NotNull()); + ++count; + } + EXPECT_THAT(count, Eq(2U)); +} + +} // namespace +} // namespace score::launch_manager::config diff --git a/src/launch_manager_daemon/config/src/flatbuffer_config_loader.cpp b/src/launch_manager_daemon/config/src/flatbuffer_config_loader.cpp new file mode 100644 index 000000000..8d18b1392 --- /dev/null +++ b/src/launch_manager_daemon/config/src/flatbuffer_config_loader.cpp @@ -0,0 +1,629 @@ +/******************************************************************************** + * 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 "flatbuffer_config_loader.hpp" +#include "new_lm_flatcfg_generated.h" + +#include "score/os/errno.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace score::launch_manager::config +{ + +namespace fb = score::launch_manager::config::fb; + +namespace +{ + +constexpr int32_t kExpectedSchemaVersion = 1; +constexpr double kSecondsToMilliseconds = 1000.0; + +uint32_t secondsToMs(double seconds) +{ + const auto result = static_cast(seconds * kSecondsToMilliseconds); + assert(!(seconds > 0.0 && result == 0U) && "Sub-millisecond precision is not supported: value rounds to 0ms"); + return result; +} + +// --- Enum conversion helpers --- + +ApplicationType convertApplicationType(fb::ApplicationType fb_type) +{ + switch (fb_type) + { + case fb::ApplicationType::Reporting: + 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; + } +} + +ProcessState convertProcessState(fb::ProcessState fb_state) +{ + switch (fb_state) + { + case fb::ProcessState::Terminated: + return ProcessState::Terminated; + case fb::ProcessState::Running: + default: + return ProcessState::Running; + } +} + +score::cpp::expected convertSchedulingPolicy(fb::SchedulingPolicy policy) +{ + switch (policy) + { + case fb::SchedulingPolicy::OTHER: + return SCHED_OTHER; + case fb::SchedulingPolicy::FIFO: + return SCHED_FIFO; + case fb::SchedulingPolicy::RR: + return SCHED_RR; + default: + LM_LOG_ERROR() << "Unsupported scheduling policy: " << static_cast(policy); + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } +} + +// --- String/vector helpers --- + +std::string safeString(const ::flatbuffers::String* s) +{ + return s ? s->str() : std::string{}; +} + +std::vector convertStringVector( + const ::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>>* vec) +{ + std::vector result; + if (vec != nullptr) + { + result.reserve(vec->size()); + for (const auto* s : *vec) + { + result.emplace_back(s ? s->str() : std::string{}); + } + } + return result; +} + +score::cpp::expected, IConfigLoader::Error> convertGidVector( + const ::flatbuffers::Vector* vec) +{ + std::vector result; + if (vec != nullptr) + { + result.reserve(vec->size()); + for (const auto val : *vec) + { + if (val < std::numeric_limits::min() || val > std::numeric_limits::max()) + { + LM_LOG_ERROR() << "Sandbox supplementary group id " << val << " is out of valid gid_t range [" << std::numeric_limits::min() << "," << std::numeric_limits::max() << "]"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + result.emplace_back(static_cast(val)); + } + } + return result; +} + +Environment convertEnvironmentalVariables( + const ::flatbuffers::Vector<::flatbuffers::Offset>* vec) +{ + Environment result; + if (vec != nullptr) + { + result.reserve(vec->size()); + for (const auto* ev : *vec) + { + if (ev != nullptr) + { + assert(ev->key() && "EnvironmentalVariable::key must never be nullptr as it is required in the schema"); + assert(ev->value() && "EnvironmentalVariable::value must never be nullptr as it is required in the schema"); + result.add(ev->key()->str(), ev->value()->str()); + } + } + } + return result; +} + +// --- Recovery action conversion --- + +score::cpp::expected, IConfigLoader::Error> convertRestartAction( + const fb::RestartAction* ra) +{ + if (ra == nullptr) + { + return std::optional{std::nullopt}; + } + if (!ra->number_of_attempts().has_value()) + { + LM_LOG_ERROR() << "RestartAction::number_of_attempts is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (!ra->delay_before_restart().has_value()) + { + LM_LOG_ERROR() << "RestartAction::delay_before_restart is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + return std::optional{ + RestartAction{*ra->number_of_attempts(), secondsToMs(*ra->delay_before_restart())}}; +} + +std::optional convertSwitchRunTargetAction(const fb::SwitchRunTargetAction* sa) +{ + if (sa == nullptr) + { + return std::nullopt; + } + assert(sa->run_target() && "SwitchRunTargetAction::run_target must never be nullptr as it is required in the schema"); + return SwitchRunTargetAction{sa->run_target()->str()}; +} + +SwitchRunTargetAction convertRequiredSwitchRunTargetAction(const fb::SwitchRunTargetAction* sa) +{ + assert(sa && "SwitchRunTargetAction must never be nullptr as it is required in the schema"); + return convertSwitchRunTargetAction(sa).value(); +} + +// --- Struct conversion helpers --- + +score::cpp::expected convertComponentAliveSupervision( + const fb::ComponentAliveSupervision* fb_cas) +{ + ComponentAliveSupervision result{}; + if (fb_cas != nullptr) + { + if (!fb_cas->reporting_cycle().has_value()) + { + LM_LOG_ERROR() << "ComponentAliveSupervision::reporting_cycle is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (!fb_cas->failed_cycles_tolerance().has_value()) + { + LM_LOG_ERROR() << "ComponentAliveSupervision::failed_cycles_tolerance is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + result.reporting_cycle_ms = secondsToMs(*fb_cas->reporting_cycle()); + result.failed_cycles_tolerance = *fb_cas->failed_cycles_tolerance(); + result.min_indications = fb_cas->min_indications().has_value() + ? std::optional{*fb_cas->min_indications()} + : std::nullopt; + result.max_indications = fb_cas->max_indications().has_value() + ? std::optional{*fb_cas->max_indications()} + : std::nullopt; + } + return result; +} + +score::cpp::expected convertApplicationProfile( + const fb::ApplicationProfile* fb_ap) +{ + ApplicationProfile result{}; + if (fb_ap != nullptr) + { + if (!fb_ap->application_type().has_value()) + { + LM_LOG_ERROR() << "ApplicationProfile::application_type is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (!fb_ap->is_self_terminating().has_value()) + { + LM_LOG_ERROR() << "ApplicationProfile::is_self_terminating is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + result.application_type = convertApplicationType(*fb_ap->application_type()); + result.is_self_terminating = *fb_ap->is_self_terminating(); + if (fb_ap->alive_supervision() != nullptr) + { + auto alive_sup = convertComponentAliveSupervision(fb_ap->alive_supervision()); + if (!alive_sup.has_value()) + { + return score::cpp::make_unexpected(alive_sup.error()); + } + result.alive_supervision = std::move(*alive_sup); + } + } + return result; +} + +score::cpp::expected convertReadyCondition(const fb::ReadyCondition* fb_rc) +{ + ReadyCondition result{}; + if (fb_rc != nullptr) + { + if (!fb_rc->process_state().has_value()) + { + LM_LOG_ERROR() << "ReadyCondition::process_state is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + result.process_state = convertProcessState(*fb_rc->process_state()); + } + return result; +} + +score::cpp::expected convertComponentProperties( + const fb::ComponentProperties* fb_cp) +{ + ComponentProperties result{}; + if (fb_cp != nullptr) + { + assert(fb_cp->binary_name() && "ComponentProperties::binary_name must never be nullptr as it is required in the schema"); + assert(fb_cp->application_profile() && "ComponentProperties::application_profile must never be nullptr as it is required in the schema"); + result.binary_name = fb_cp->binary_name()->str(); + auto app_profile = convertApplicationProfile(fb_cp->application_profile()); + if (!app_profile.has_value()) + { + return score::cpp::make_unexpected(app_profile.error()); + } + result.application_profile = std::move(*app_profile); + result.depends_on = convertStringVector(fb_cp->depends_on()); + 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()) + { + return score::cpp::make_unexpected(ready_cond.error()); + } + result.ready_condition = std::move(*ready_cond); + } + } + return result; +} + +score::cpp::expected convertSandbox(const fb::Sandbox* fb_sb) +{ + assert(fb_sb && "Sandbox must never be nullptr as it is required in the schema"); + if (!fb_sb->uid().has_value()) + { + LM_LOG_ERROR() << "Sandbox::uid is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (!fb_sb->gid().has_value()) + { + LM_LOG_ERROR() << "Sandbox::gid is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + const auto fb_uid = *fb_sb->uid(); + const auto fb_gid = *fb_sb->gid(); + if (fb_uid < std::numeric_limits::min() || fb_uid > std::numeric_limits::max()) + { + LM_LOG_ERROR() << "Sandbox uid " << fb_uid << " is out of valid uid_t range [" << std::numeric_limits::min() << "," << std::numeric_limits::max() << "]"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (fb_gid < std::numeric_limits::min() || fb_gid > std::numeric_limits::max()) + { + LM_LOG_ERROR() << "Sandbox gid " << fb_gid << " is out of valid gid_t range [" << std::numeric_limits::min() << "," << std::numeric_limits::max() << "]"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (!fb_sb->scheduling_policy().has_value()) + { + LM_LOG_ERROR() << "Sandbox::scheduling_policy is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + auto scheduling_policy = convertSchedulingPolicy(*fb_sb->scheduling_policy()); + if (!scheduling_policy.has_value()) + { + return score::cpp::make_unexpected(scheduling_policy.error()); + } + if (!fb_sb->scheduling_priority().has_value()) + { + LM_LOG_ERROR() << "Sandbox::scheduling_priority is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + Sandbox result{}; + result.uid = static_cast(fb_uid); + result.gid = static_cast(fb_gid); + auto supplementary_gids = convertGidVector(fb_sb->supplementary_group_ids()); + if (!supplementary_gids.has_value()) + { + return score::cpp::make_unexpected(supplementary_gids.error()); + } + result.supplementary_group_ids = std::move(*supplementary_gids); + result.security_policy = fb_sb->security_policy() != nullptr + ? std::optional{fb_sb->security_policy()->str()} + : std::nullopt; + result.scheduling_policy = *scheduling_policy; + result.scheduling_priority = *fb_sb->scheduling_priority(); + result.max_memory_usage = + fb_sb->max_memory_usage().has_value() ? std::optional{*fb_sb->max_memory_usage()} : std::nullopt; + result.max_cpu_usage = + fb_sb->max_cpu_usage().has_value() ? std::optional{*fb_sb->max_cpu_usage()} : std::nullopt; + return result; +} + +score::cpp::expected convertDeploymentConfig(const fb::DeploymentConfig* fb_dc) +{ + DeploymentConfig result{}; + if (fb_dc != nullptr) + { + assert(fb_dc->bin_dir() && "DeploymentConfig::bin_dir must never be nullptr as it is required in the schema"); + assert(fb_dc->working_dir() && "DeploymentConfig::working_dir must never be nullptr as it is required in the schema"); + assert(fb_dc->sandbox() && "DeploymentConfig::sandbox must never be nullptr as it is required in the schema"); + if (!fb_dc->ready_timeout().has_value()) + { + LM_LOG_ERROR() << "DeploymentConfig::ready_timeout is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (!fb_dc->shutdown_timeout().has_value()) + { + LM_LOG_ERROR() << "DeploymentConfig::shutdown_timeout is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + result.ready_timeout_ms = secondsToMs(*fb_dc->ready_timeout()); + result.shutdown_timeout_ms = secondsToMs(*fb_dc->shutdown_timeout()); + result.environmental_variables = convertEnvironmentalVariables(fb_dc->environmental_variables()); + result.bin_dir = fb_dc->bin_dir()->str(); + result.working_dir = fb_dc->working_dir()->str(); + auto ready_recovery = convertRestartAction(fb_dc->ready_recovery_action()); + if (!ready_recovery.has_value()) + { + return score::cpp::make_unexpected(ready_recovery.error()); + } + result.ready_recovery_action = std::move(*ready_recovery); + result.recovery_action = convertSwitchRunTargetAction(fb_dc->recovery_action()); + auto sandbox = convertSandbox(fb_dc->sandbox()); + if (!sandbox.has_value()) + { + return score::cpp::make_unexpected(sandbox.error()); + } + result.sandbox = std::move(*sandbox); + } + return result; +} + +score::cpp::expected convertComponent(const fb::Component* fb_comp) +{ + ComponentConfig result{}; + if (fb_comp != nullptr) + { + assert(fb_comp->name() && "Component::name must never be nullptr as it is required in the schema"); + assert(fb_comp->component_properties() && "Component::component_properties must never be nullptr as it is required in the schema"); + assert(fb_comp->deployment_config() && "Component::deployment_config must never be nullptr as it is required in the schema"); + result.name = fb_comp->name()->str(); + result.description = safeString(fb_comp->description()); + auto component_properties = convertComponentProperties(fb_comp->component_properties()); + if (!component_properties.has_value()) + { + return score::cpp::make_unexpected(component_properties.error()); + } + result.component_properties = std::move(*component_properties); + auto deployment_config = convertDeploymentConfig(fb_comp->deployment_config()); + if (!deployment_config.has_value()) + { + return score::cpp::make_unexpected(deployment_config.error()); + } + result.deployment_config = std::move(*deployment_config); + } + return result; +} + +score::cpp::expected convertRunTarget(const fb::RunTarget* fb_rt) +{ + RunTargetConfig result{}; + if (fb_rt != nullptr) + { + assert(fb_rt->name() && "RunTarget::name must never be nullptr as it is required in the schema"); + assert(fb_rt->recovery_action() && "RunTarget::recovery_action must never be nullptr as it is required in the schema"); + if (!fb_rt->transition_timeout().has_value()) + { + LM_LOG_ERROR() << "RunTarget::transition_timeout is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + result.name = fb_rt->name()->str(); + result.description = safeString(fb_rt->description()); + result.depends_on = convertStringVector(fb_rt->depends_on()); + result.transition_timeout_ms = secondsToMs(*fb_rt->transition_timeout()); + result.recovery_action = convertRequiredSwitchRunTargetAction(fb_rt->recovery_action()); + } + return result; +} + +score::cpp::expected convertFallbackRunTarget( + const fb::FallbackRunTarget* fb_frt) +{ + FallbackRunTargetConfig result{}; + if (fb_frt != nullptr) + { + if (!fb_frt->transition_timeout().has_value()) + { + LM_LOG_ERROR() << "FallbackRunTarget::transition_timeout is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + result.description = safeString(fb_frt->description()); + result.depends_on = convertStringVector(fb_frt->depends_on()); + result.transition_timeout_ms = secondsToMs(*fb_frt->transition_timeout()); + } + return result; +} + +score::cpp::expected convertAliveSupervision( + const fb::AliveSupervision* fb_as) +{ + if (fb_as == nullptr) + { + return AliveSupervisionConfig{}; + } + if (!fb_as->evaluation_cycle().has_value()) + { + LM_LOG_ERROR() << "AliveSupervision::evaluation_cycle is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + return AliveSupervisionConfig{secondsToMs(*fb_as->evaluation_cycle())}; +} + +score::cpp::expected, IConfigLoader::Error> convertWatchdog(const fb::Watchdog* fb_wd) +{ + if (fb_wd == nullptr) + { + return std::optional{std::nullopt}; + } + assert(fb_wd->device_file_path() && "Watchdog::device_file_path must never be nullptr as it is required in the schema"); + if (!fb_wd->max_timeout().has_value()) + { + LM_LOG_ERROR() << "Watchdog::max_timeout is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (!fb_wd->deactivate_on_shutdown().has_value()) + { + LM_LOG_ERROR() << "Watchdog::deactivate_on_shutdown is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (!fb_wd->require_magic_close().has_value()) + { + LM_LOG_ERROR() << "Watchdog::require_magic_close is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + WatchdogConfig result{}; + result.device_file_path = fb_wd->device_file_path()->str(); + result.max_timeout_ms = secondsToMs(*fb_wd->max_timeout()); + result.deactivate_on_shutdown = *fb_wd->deactivate_on_shutdown(); + result.require_magic_close = *fb_wd->require_magic_close(); + return std::optional{std::move(result)}; +} + +} // anonymous namespace + +namespace details +{ + +// --- File I/O error mapping --- + +IConfigLoader::Error mapOsError(const score::os::Error& error) +{ + if (error == score::os::Error::Code::kNoSuchFileOrDirectory) + { + return IConfigLoader::Error::FileNotFound; + } + if (error == score::os::Error::Code::kPermissionDenied) + { + return IConfigLoader::Error::InsufficientPermission; + } + return IConfigLoader::Error::GeneralError; +} + +// --- FlatBuffer parsing and conversion --- + +score::cpp::expected parseFlatbuffer(const std::vector& buffer) +{ + ::flatbuffers::Verifier verifier(buffer.data(), buffer.size()); + if (!fb::VerifyLaunchManagerConfigBuffer(verifier)) + { + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + + const auto* config = fb::GetLaunchManagerConfig(buffer.data()); + if (config == nullptr) + { + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + + if (!config->schema_version().has_value()) + { + LM_LOG_ERROR() << "LaunchManagerConfig::schema_version is required but missing"; + return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); + } + if (*config->schema_version() != kExpectedSchemaVersion) + { + return score::cpp::make_unexpected(IConfigLoader::Error::UnsupportedVersion); + } + + ConfigBuilder builder; + + assert(config->initial_run_target() && "LaunchManagerConfig::initial_run_target must never be nullptr as it is required in the schema"); + assert(config->components() && "LaunchManagerConfig::components must never be nullptr as it is required in the schema"); + assert(config->run_targets() && "LaunchManagerConfig::run_targets must never be nullptr as it is required in the schema"); + builder.setInitialRunTarget(config->initial_run_target()->str()); + + { + std::vector components; + components.reserve(config->components()->size()); + for (const auto* comp : *config->components()) + { + if (comp != nullptr) + { + auto component = convertComponent(comp); + if (!component.has_value()) + { + return score::cpp::make_unexpected(component.error()); + } + components.emplace_back(std::move(*component)); + } + } + builder.setComponents(std::move(components)); + } + + { + std::vector run_targets; + run_targets.reserve(config->run_targets()->size()); + for (const auto* rt : *config->run_targets()) + { + if (rt != nullptr) + { + auto run_target = convertRunTarget(rt); + if (!run_target.has_value()) + { + return score::cpp::make_unexpected(run_target.error()); + } + run_targets.emplace_back(std::move(*run_target)); + } + } + builder.setRunTargets(std::move(run_targets)); + } + + assert(config->fallback_run_target() && "LaunchManagerConfig::fallback_run_target must never be nullptr as it is required in the schema"); + auto fallback = convertFallbackRunTarget(config->fallback_run_target()); + if (!fallback.has_value()) + { + return score::cpp::make_unexpected(fallback.error()); + } + builder.setFallbackRunTarget(std::move(*fallback)); + + assert(config->alive_supervision() && "LaunchManagerConfig::alive_supervision must never be nullptr as it is required in the schema"); + auto alive_sup = convertAliveSupervision(config->alive_supervision()); + if (!alive_sup.has_value()) + { + return score::cpp::make_unexpected(alive_sup.error()); + } + builder.setAliveSupervision(std::move(*alive_sup)); + + auto wd = convertWatchdog(config->watchdog()); + if (!wd.has_value()) + { + return score::cpp::make_unexpected(wd.error()); + } + if (wd->has_value()) + { + builder.setWatchdog(std::move(**wd)); + } + + return builder.build(); +} + +} // namespace details +} // namespace score::launch_manager::config diff --git a/src/launch_manager_daemon/config/src/flatbuffer_config_loader_UT.cpp b/src/launch_manager_daemon/config/src/flatbuffer_config_loader_UT.cpp new file mode 100644 index 000000000..c82617110 --- /dev/null +++ b/src/launch_manager_daemon/config/src/flatbuffer_config_loader_UT.cpp @@ -0,0 +1,1290 @@ +/******************************************************************************** + * 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 "flatbuffer_config_loader.hpp" +#include "new_lm_flatcfg_generated.h" + +#include "score/filesystem/path.h" +#include "score/os/errno.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace score::launch_manager::config +{ +namespace +{ + +namespace fb = score::launch_manager::config::fb; + +using ::testing::Eq; +using ::testing::IsFalse; +using ::testing::IsNull; +using ::testing::IsTrue; +using ::testing::StrEq; + +const score::filesystem::Path kTestPath{"/tmp/test_config.bin"}; + +std::vector finishBuffer(::flatbuffers::FlatBufferBuilder& fbb, + ::flatbuffers::Offset root) +{ + fb::FinishLaunchManagerConfigBuffer(fbb, root); + const auto* buf = fbb.GetBufferPointer(); + return {buf, buf + fbb.GetSize()}; +} + +// ============================================================================ +// Helper functions to reduce test boilerplate +// ============================================================================ + +::flatbuffers::Offset buildDefaultSandbox(::flatbuffers::FlatBufferBuilder& fbb) +{ + return fb::CreateSandbox(fbb, + 0 /*uid*/, + 0 /*gid*/, + 0 /*supplementary_group_ids*/, + 0 /*security_policy*/, + fb::SchedulingPolicy::OTHER, + 0 /*scheduling_priority*/); +} + +::flatbuffers::Offset buildDefaultComponentProperties(::flatbuffers::FlatBufferBuilder& fbb) +{ + auto bin_name = fbb.CreateString("default_bin"); + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, false); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + return fb::CreateComponentProperties( + fbb, bin_name, app_profile, 0 /*depends_on*/, 0 /*process_arguments*/, ready_cond); +} + +::flatbuffers::Offset buildDefaultDeploymentConfig(::flatbuffers::FlatBufferBuilder& fbb) +{ + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto sandbox = buildDefaultSandbox(fbb); + return fb::CreateDeploymentConfig( + fbb, 1.0 /*ready_timeout*/, 1.0 /*shutdown_timeout*/, 0 /*environmental_variables*/, + bin_dir, work_dir, 0 /*ready_recovery_action*/, 0 /*recovery_action*/, sandbox); +} + +::flatbuffers::Offset buildDefaultComponent( + ::flatbuffers::FlatBufferBuilder& fbb, + const char* name, + ::flatbuffers::Offset comp_props = 0, + ::flatbuffers::Offset deploy = 0) +{ + if (comp_props.IsNull()) + { + comp_props = buildDefaultComponentProperties(fbb); + } + if (deploy.IsNull()) + { + deploy = buildDefaultDeploymentConfig(fbb); + } + auto comp_name = fbb.CreateString(name); + return fb::CreateComponent(fbb, comp_name, 0 /*description*/, comp_props, deploy); +} + +std::vector buildConfigWithComponents( + ::flatbuffers::FlatBufferBuilder& fbb, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> comps) +{ + auto fallback = fb::CreateFallbackRunTarget(fbb, 0 /*description*/, 0 /*depends_on*/, 1.0 /*transition_timeout*/); + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0 /*evaluation_cycle*/); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto irt = fbb.CreateString("Startup"); + auto config = fb::CreateLaunchManagerConfig(fbb, 1 /*schema_version*/, comps, rts, irt, fallback, alive_sup); + return finishBuffer(fbb, config); +} + +std::vector buildConfigWithRunTargets( + ::flatbuffers::FlatBufferBuilder& fbb, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> rts) +{ + auto fallback = fb::CreateFallbackRunTarget(fbb, 0 /*description*/, 0 /*depends_on*/, 1.0 /*transition_timeout*/); + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0 /*evaluation_cycle*/); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto irt = fbb.CreateString("Startup"); + auto config = fb::CreateLaunchManagerConfig(fbb, 1 /*schema_version*/, comps, rts, irt, fallback, alive_sup); + return finishBuffer(fbb, config); +} + +// ============================================================================ + +struct MockBufferLoader +{ + static score::os::Result> load(const score::filesystem::Path&) + { + return result_; + } + static score::os::Result> result_; +}; + +score::os::Result> MockBufferLoader::result_{std::vector{}}; + +class FlatbufferConfigLoaderTest : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + MockBufferLoader::result_ = std::vector{}; + } + + std::vector buildMinimalConfig(int32_t schema_version = 1, const char* initial_run_target = "Startup") + { + ::flatbuffers::FlatBufferBuilder fbb; + auto irt = fbb.CreateString(initial_run_target); + auto fallback = + fb::CreateFallbackRunTarget(fbb, 0 /*description*/, 0 /*depends_on*/, 1.0 /*transition_timeout*/); + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0 /*evaluation_cycle*/); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto config = + fb::CreateLaunchManagerConfig(fbb, schema_version, comps, rts, irt, fallback, alive_sup); + return finishBuffer(fbb, config); + } + + score::cpp::expected loadBuffer(const std::vector& buffer) + { + MockBufferLoader::result_ = buffer; + return loader_.load(kTestPath); + } + + FlatbufferConfigLoaderImpl loader_; +}; + +// ============================================================================ +// Happy path tests +// ============================================================================ + +TEST_F(FlatbufferConfigLoaderTest, LoadMinimalConfig) +{ + RecordProperty("Description", "Loads a minimal config with only required fields."); + + auto result = loadBuffer(buildMinimalConfig(1, "Startup")); + + ASSERT_THAT(result.has_value(), IsTrue()); + EXPECT_THAT(result->initialRunTarget(), Eq("Startup")); + EXPECT_THAT(result->components().empty(), IsTrue()); + EXPECT_THAT(result->runTargets().empty(), IsTrue()); + EXPECT_THAT(result->watchdog().has_value(), IsFalse()); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadSingleComponent) +{ + RecordProperty("Description", "Loads a config with one fully-populated component."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto alive_sup = fb::CreateComponentAliveSupervision( + fbb, 0.5 /*reporting_cycle*/, 2 /*failed_cycles_tolerance*/, 1 /*min_indications*/, 3 /*max_indications*/); + auto app_profile = fb::CreateApplicationProfile( + fbb, fb::ApplicationType::Reporting_And_Supervised, true /*is_self_terminating*/, alive_sup); + auto bin_name = fbb.CreateString("my_binary"); + auto dep = fbb.CreateString("other_comp"); + auto deps_vec = fbb.CreateVector(std::vector<::flatbuffers::Offset<::flatbuffers::String>>{dep}); + auto arg = fbb.CreateString("--verbose"); + auto args_vec = fbb.CreateVector(std::vector<::flatbuffers::Offset<::flatbuffers::String>>{arg}); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + auto comp_props = fb::CreateComponentProperties(fbb, bin_name, app_profile, deps_vec, args_vec, 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>{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.name, Eq("TestComponent")); + EXPECT_THAT(comp.description, Eq("A test component")); + EXPECT_THAT(comp.component_properties.binary_name, Eq("my_binary")); + EXPECT_THAT(comp.component_properties.application_profile.application_type, + Eq(ApplicationType::ReportingAndSupervised)); + EXPECT_THAT(comp.component_properties.application_profile.is_self_terminating, IsTrue()); + ASSERT_THAT(comp.component_properties.application_profile.alive_supervision.has_value(), IsTrue()); + EXPECT_THAT(comp.component_properties.application_profile.alive_supervision->reporting_cycle_ms, Eq(500U)); + EXPECT_THAT(comp.component_properties.application_profile.alive_supervision->failed_cycles_tolerance, Eq(2U)); + ASSERT_THAT(comp.component_properties.application_profile.alive_supervision->min_indications.has_value(), IsTrue()); + EXPECT_THAT(*comp.component_properties.application_profile.alive_supervision->min_indications, Eq(1U)); + ASSERT_THAT(comp.component_properties.application_profile.alive_supervision->max_indications.has_value(), IsTrue()); + EXPECT_THAT(*comp.component_properties.application_profile.alive_supervision->max_indications, Eq(3U)); + ASSERT_THAT(comp.component_properties.depends_on.size(), Eq(1U)); + 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.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, LoadRunTargets) +{ + RecordProperty("Description", "Loads run targets with dependencies and transition timeout."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto switch_target = fbb.CreateString("SafeState"); + auto switch_action = fb::CreateSwitchRunTargetAction(fbb, switch_target); + auto rt_name = fbb.CreateString("Startup"); + auto rt_desc = fbb.CreateString("Initial state"); + auto rt_dep = fbb.CreateString("component_a"); + auto rt_deps = fbb.CreateVector(std::vector<::flatbuffers::Offset<::flatbuffers::String>>{rt_dep}); + auto rt = fb::CreateRunTarget(fbb, rt_name, rt_desc, rt_deps, 5.0 /*transition_timeout*/, switch_action); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{rt}); + + auto result = loadBuffer(buildConfigWithRunTargets(fbb, rts)); + + ASSERT_THAT(result.has_value(), IsTrue()); + ASSERT_THAT(result->runTargets().size(), Eq(1U)); + + const auto& target = result->runTargets()[0]; + EXPECT_THAT(target.name, Eq("Startup")); + EXPECT_THAT(target.description, Eq("Initial state")); + ASSERT_THAT(target.depends_on.size(), Eq(1U)); + EXPECT_THAT(target.depends_on[0], Eq("component_a")); + EXPECT_THAT(target.transition_timeout_ms, Eq(5000U)); + EXPECT_THAT(target.recovery_action.run_target, Eq("SafeState")); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadFallbackRunTarget) +{ + RecordProperty("Description", "Loads fallback run target with all fields."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto fb_desc = fbb.CreateString("Fallback state"); + auto fb_dep = fbb.CreateString("critical_comp"); + auto fb_deps = fbb.CreateVector(std::vector<::flatbuffers::Offset<::flatbuffers::String>>{fb_dep}); + auto fallback = fb::CreateFallbackRunTarget(fbb, fb_desc, fb_deps, 10.0 /*transition_timeout*/); + + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0 /*evaluation_cycle*/); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto irt = fbb.CreateString("Startup"); + auto config = fb::CreateLaunchManagerConfig(fbb, 1 /*schema_version*/, comps, rts, irt, fallback, alive_sup); + + auto result = loadBuffer(finishBuffer(fbb, config)); + + ASSERT_THAT(result.has_value(), IsTrue()); + const auto& fb = result->fallbackRunTarget(); + EXPECT_THAT(fb.description, Eq("Fallback state")); + ASSERT_THAT(fb.depends_on.size(), Eq(1U)); + EXPECT_THAT(fb.depends_on[0], Eq("critical_comp")); + EXPECT_THAT(fb.transition_timeout_ms, Eq(10000U)); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadAliveSupervision) +{ + RecordProperty("Description", "Loads global alive supervision config."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto alive_sup = fb::CreateAliveSupervision(fbb, 0.25 /*evaluation_cycle*/); + auto fallback = fb::CreateFallbackRunTarget(fbb, 0 /*description*/, 0 /*depends_on*/, 1.0 /*transition_timeout*/); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto irt = fbb.CreateString("Startup"); + auto config = fb::CreateLaunchManagerConfig(fbb, 1 /*schema_version*/, comps, rts, irt, fallback, alive_sup); + + auto result = loadBuffer(finishBuffer(fbb, config)); + + ASSERT_THAT(result.has_value(), IsTrue()); + EXPECT_THAT(result->aliveSupervision().evaluation_cycle_ms, Eq(250U)); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadWatchdog) +{ + RecordProperty("Description", "Loads watchdog config with all fields."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto dev_path = fbb.CreateString("/dev/watchdog0"); + auto watchdog = fb::CreateWatchdog( + fbb, dev_path, 30.0 /*max_timeout*/, true /*deactivate_on_shutdown*/, false /*require_magic_close*/); + + auto fallback = fb::CreateFallbackRunTarget(fbb, 0 /*description*/, 0 /*depends_on*/, 1.0 /*transition_timeout*/); + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0 /*evaluation_cycle*/); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto irt = fbb.CreateString("Startup"); + auto config = fb::CreateLaunchManagerConfig(fbb, 1 /*schema_version*/, comps, rts, irt, fallback, alive_sup, watchdog); + + auto result = loadBuffer(finishBuffer(fbb, config)); + + ASSERT_THAT(result.has_value(), IsTrue()); + ASSERT_THAT(result->watchdog().has_value(), IsTrue()); + + const auto& wd = result->watchdog().value(); + EXPECT_THAT(wd.device_file_path, Eq("/dev/watchdog0")); + EXPECT_THAT(wd.max_timeout_ms, Eq(30000U)); + EXPECT_THAT(wd.deactivate_on_shutdown, IsTrue()); + EXPECT_THAT(wd.require_magic_close, IsFalse()); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadRestartRecoveryAction) +{ + RecordProperty("Description", "Recovery action variant holds RestartAction with correct fields on a component."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto restart = fb::CreateRestartAction(fbb, 3 /*number_of_attempts*/, 1.5 /*delay_before_restart*/); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto sandbox = buildDefaultSandbox(fbb); + auto deploy = fb::CreateDeploymentConfig(fbb, + 1.0 /*ready_timeout*/, + 1.0 /*shutdown_timeout*/, + 0 /*environmental_variables*/, + bin_dir, + work_dir, + restart, + 0 /*recovery_action*/, + sandbox); + + auto comp = buildDefaultComponent(fbb, "restart_comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + ASSERT_THAT(result->components().size(), Eq(1U)); + ASSERT_THAT(result->components()[0].deployment_config.ready_recovery_action.has_value(), IsTrue()); + + const auto& ra = result->components()[0].deployment_config.ready_recovery_action.value(); + EXPECT_THAT(ra.number_of_attempts, Eq(3U)); + EXPECT_THAT(ra.delay_before_restart_ms, Eq(1500U)); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadSwitchRunTargetAction) +{ + RecordProperty("Description", "Recovery action variant holds SwitchRunTargetAction."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto target_name = fbb.CreateString("Fallback"); + auto switch_action = fb::CreateSwitchRunTargetAction(fbb, target_name); + auto rt_name = fbb.CreateString("Startup"); + auto rt = fb::CreateRunTarget( + fbb, rt_name, 0 /*description*/, 0 /*depends_on*/, 1.0 /*transition_timeout*/, switch_action); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{rt}); + + auto result = loadBuffer(buildConfigWithRunTargets(fbb, rts)); + + ASSERT_THAT(result.has_value(), IsTrue()); + EXPECT_THAT(result->runTargets()[0].recovery_action.run_target, Eq("Fallback")); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadSandbox) +{ + RecordProperty("Description", "Loads sandbox config including optional fields."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto sec_policy = fbb.CreateString("strict"); + auto supp_gids = fbb.CreateVector(std::vector{100, 200}); + auto sandbox = fb::CreateSandbox(fbb, + 1000 /*uid*/, + 1000 /*gid*/, + supp_gids, + sec_policy, + fb::SchedulingPolicy::FIFO, + 50 /*scheduling_priority*/, + 4096 /*max_memory_usage*/, + 80 /*max_cpu_usage*/); + + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, + 0.5 /*ready_timeout*/, + 0.5 /*shutdown_timeout*/, + 0 /*environmental_variables*/, + bin_dir, + work_dir, + 0 /*ready_recovery_action*/, + 0 /*recovery_action*/, + sandbox); + + auto comp = buildDefaultComponent(fbb, "sandboxed_comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + ASSERT_THAT(result->components().size(), Eq(1U)); + + const auto& sb = result->components()[0].deployment_config.sandbox; + EXPECT_THAT(sb.uid, Eq(1000U)); + EXPECT_THAT(sb.gid, Eq(1000U)); + EXPECT_THAT(sb.supplementary_group_ids, Eq(std::vector{100, 200})); + ASSERT_THAT(sb.security_policy.has_value(), IsTrue()); + EXPECT_THAT(sb.security_policy.value(), Eq("strict")); + EXPECT_THAT(sb.scheduling_policy, Eq(SCHED_FIFO)); + EXPECT_THAT(sb.scheduling_priority, Eq(50)); + ASSERT_THAT(sb.max_memory_usage.has_value(), IsTrue()); + EXPECT_THAT(sb.max_memory_usage.value(), Eq(4096U)); + ASSERT_THAT(sb.max_cpu_usage.has_value(), IsTrue()); + EXPECT_THAT(sb.max_cpu_usage.value(), Eq(80U)); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadComponentAliveSupervision) +{ + RecordProperty("Description", "Per-component alive supervision is mapped correctly."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto comp_alive_sup = fb::CreateComponentAliveSupervision( + fbb, 1.0 /*reporting_cycle*/, 3 /*failed_cycles_tolerance*/, 2 /*min_indications*/, 5 /*max_indications*/); + auto app_profile = fb::CreateApplicationProfile( + fbb, fb::ApplicationType::Reporting_And_Supervised, false /*is_self_terminating*/, comp_alive_sup); + auto bin_name = fbb.CreateString("supervised_bin"); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + auto comp_props = fb::CreateComponentProperties( + fbb, bin_name, app_profile, 0 /*depends_on*/, 0 /*process_arguments*/, ready_cond); + + auto comp = buildDefaultComponent(fbb, "supervised_comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + const auto& as = result->components()[0].component_properties.application_profile.alive_supervision; + ASSERT_THAT(as.has_value(), IsTrue()); + EXPECT_THAT(as->reporting_cycle_ms, Eq(1000U)); + EXPECT_THAT(as->failed_cycles_tolerance, Eq(3U)); + ASSERT_THAT(as->min_indications.has_value(), IsTrue()); + EXPECT_THAT(*as->min_indications, Eq(2U)); + ASSERT_THAT(as->max_indications.has_value(), IsTrue()); + EXPECT_THAT(*as->max_indications, Eq(5U)); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadEnvironmentalVariables) +{ + RecordProperty("Description", "Environmental variables are stored as key=value strings."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto k1 = fbb.CreateString("PATH"); + auto v1 = fbb.CreateString("/usr/bin"); + auto ev1 = fb::CreateEnvironmentalVariable(fbb, k1, v1); + auto k2 = fbb.CreateString("HOME"); + auto v2 = fbb.CreateString("/root"); + auto ev2 = fb::CreateEnvironmentalVariable(fbb, k2, v2); + auto env_vars = fbb.CreateVector(std::vector<::flatbuffers::Offset>{ev1, ev2}); + + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto sandbox = buildDefaultSandbox(fbb); + auto deploy = fb::CreateDeploymentConfig( + fbb, 0.5 /*ready_timeout*/, 0.5 /*shutdown_timeout*/, env_vars, + bin_dir, work_dir, 0 /*ready_recovery_action*/, 0 /*recovery_action*/, sandbox); + + auto comp = buildDefaultComponent(fbb, "env_comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + const auto& env = result->components()[0].deployment_config.environmental_variables; + ASSERT_THAT(env.size(), Eq(2U)); + auto it = env.begin(); + EXPECT_THAT(it->key(), Eq("PATH")); + EXPECT_THAT(it->value(), Eq("/usr/bin")); + ++it; + EXPECT_THAT(it->key(), Eq("HOME")); + EXPECT_THAT(it->value(), Eq("/root")); + ASSERT_THAT(env.size(), Eq(2U)); + EXPECT_THAT(env.envp()[0], StrEq("PATH=/usr/bin")); + EXPECT_THAT(env.envp()[1], StrEq("HOME=/root")); + EXPECT_THAT(env.envp()[2], IsNull()); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadMultipleComponents) +{ + RecordProperty("Description", "Multiple components are all present and in order."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto comp_a = buildDefaultComponent(fbb, "CompA"); + auto comp_b = buildDefaultComponent(fbb, "CompB"); + auto comp_c = buildDefaultComponent(fbb, "CompC"); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp_a, comp_b, comp_c}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + ASSERT_THAT(result->components().size(), Eq(3U)); + EXPECT_THAT(result->components()[0].name, Eq("CompA")); + EXPECT_THAT(result->components()[1].name, Eq("CompB")); + EXPECT_THAT(result->components()[2].name, Eq("CompC")); +} + +// ============================================================================ +// Optional / required field tests +// ============================================================================ + +TEST_F(FlatbufferConfigLoaderTest, OptionalWatchdogAbsent) +{ + RecordProperty("Description", "When no watchdog is present, it is nullopt."); + + auto result = loadBuffer(buildMinimalConfig()); + + ASSERT_THAT(result.has_value(), IsTrue()); + EXPECT_THAT(result->watchdog().has_value(), IsFalse()); +} + +TEST_F(FlatbufferConfigLoaderTest, OptionalReadyConditionAbsent) +{ + RecordProperty("Description", "When no ready_condition is present on a component, it is nullopt."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto bin_name = fbb.CreateString("no_rc_bin"); + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, false); + auto comp_props = fb::CreateComponentProperties(fbb, bin_name, app_profile); + + auto comp = buildDefaultComponent(fbb, "no_rc_comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + EXPECT_THAT(result->components()[0].component_properties.ready_condition.has_value(), IsFalse()); +} + +TEST_F(FlatbufferConfigLoaderTest, OptionalAliveSupervisionIndicationsAbsent) +{ + RecordProperty("Description", "When min/max_indications are absent, they are nullopt."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto comp_alive_sup = fb::CreateComponentAliveSupervision(fbb, 1.0 /*reporting_cycle*/, 3 /*failed_cycles_tolerance*/); + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, false, comp_alive_sup); + auto bin_name = fbb.CreateString("no_ind_bin"); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + auto comp_props = fb::CreateComponentProperties(fbb, bin_name, app_profile, 0, 0, ready_cond); + + auto comp = buildDefaultComponent(fbb, "no_ind_comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + const auto& as = result->components()[0].component_properties.application_profile.alive_supervision; + ASSERT_THAT(as.has_value(), IsTrue()); + EXPECT_THAT(as->min_indications.has_value(), IsFalse()); + EXPECT_THAT(as->max_indications.has_value(), IsFalse()); +} + +// ============================================================================ +// Enum mapping tests +// ============================================================================ + +TEST_F(FlatbufferConfigLoaderTest, MapNativeApplicationType) +{ + RecordProperty("Description", "ApplicationType_Native maps to ApplicationType::Native."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, false); + auto bin_name = fbb.CreateString("native_bin"); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + auto comp_props = fb::CreateComponentProperties( + fbb, bin_name, app_profile, 0 /*depends_on*/, 0 /*process_arguments*/, ready_cond); + + auto comp = buildDefaultComponent(fbb, "native_comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + EXPECT_THAT(result->components()[0].component_properties.application_profile.application_type, + Eq(ApplicationType::Native)); +} + +TEST_F(FlatbufferConfigLoaderTest, MapReportingAndSupervisedType) +{ + RecordProperty("Description", + "ApplicationType_Reporting_And_Supervised maps to ApplicationType::ReportingAndSupervised."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Reporting_And_Supervised, false); + auto bin_name = fbb.CreateString("supervised_bin"); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + auto comp_props = fb::CreateComponentProperties( + fbb, bin_name, app_profile, 0 /*depends_on*/, 0 /*process_arguments*/, ready_cond); + + auto comp = buildDefaultComponent(fbb, "supervised_comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + EXPECT_THAT(result->components()[0].component_properties.application_profile.application_type, + Eq(ApplicationType::ReportingAndSupervised)); +} + +TEST_F(FlatbufferConfigLoaderTest, MapTerminatedProcessState) +{ + RecordProperty("Description", "ProcessState_Terminated maps to ProcessState::Terminated."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, false); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Terminated); + auto bin_name = fbb.CreateString("term_bin"); + auto comp_props = fb::CreateComponentProperties( + fbb, bin_name, app_profile, 0 /*depends_on*/, 0 /*process_arguments*/, ready_cond); + + auto comp = buildDefaultComponent(fbb, "term_comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + ASSERT_THAT(result->components()[0].component_properties.ready_condition.has_value(), IsTrue()); + EXPECT_THAT(result->components()[0].component_properties.ready_condition->process_state, + Eq(ProcessState::Terminated)); +} + +// ============================================================================ +// Error path tests +// ============================================================================ + +TEST_F(FlatbufferConfigLoaderTest, LoadBufferFailsWithNoSuchFileReturnsFileNotFound) +{ + RecordProperty("Description", "When LoadBuffer fails with ENOENT, returns FileNotFound error."); + + MockBufferLoader::result_ = score::cpp::make_unexpected(score::os::Error::createFromErrno(ENOENT)); + + auto result = loader_.load(kTestPath); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::FileNotFound)); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadBufferFailsWithPermissionDeniedReturnsInsufficientPermission) +{ + RecordProperty("Description", "When LoadBuffer fails with EACCES, returns InsufficientPermission error."); + + MockBufferLoader::result_ = score::cpp::make_unexpected(score::os::Error::createFromErrno(EACCES)); + + auto result = loader_.load(kTestPath); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InsufficientPermission)); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadBufferFailsWithGenericErrorReturnsGeneralError) +{ + RecordProperty("Description", "When LoadBuffer fails with a generic OS error, returns GeneralError."); + + MockBufferLoader::result_ = score::cpp::make_unexpected(score::os::Error::createFromErrno(EIO)); + + auto result = loader_.load(kTestPath); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::GeneralError)); +} + +TEST_F(FlatbufferConfigLoaderTest, CorruptedBufferReturnsInvalidFormat) +{ + RecordProperty("Description", "Corrupted binary data returns InvalidFormat error."); + + auto result = loadBuffer({0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01, 0x02, 0x03}); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, WrongSchemaVersionReturnsUnsupportedVersion) +{ + RecordProperty("Description", "A config with an unexpected schema_version returns UnsupportedVersion."); + + auto result = loadBuffer(buildMinimalConfig(99, "Startup")); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::UnsupportedVersion)); +} + +TEST_F(FlatbufferConfigLoaderTest, SandboxUidOutOfRangeReturnsInvalidFormat) +{ + RecordProperty("Description", "A sandbox uid exceeding uid_t range returns InvalidFormat."); + + if constexpr (std::numeric_limits::max() >= std::numeric_limits::max()) + { + GTEST_SKIP() << "uid_t range covers all uint32_t values on this platform"; + } + + ::flatbuffers::FlatBufferBuilder fbb; + + auto sandbox = fb::CreateSandbox(fbb, + std::numeric_limits::max() /*uid*/, + 1000 /*gid*/, + 0 /*supplementary_group_ids*/, + 0 /*security_policy*/, + fb::SchedulingPolicy::OTHER, + 0 /*scheduling_priority*/); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "bad_uid_comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, SandboxGidOutOfRangeReturnsInvalidFormat) +{ + RecordProperty("Description", "A sandbox gid exceeding gid_t range returns InvalidFormat."); + + if constexpr (std::numeric_limits::max() >= std::numeric_limits::max()) + { + GTEST_SKIP() << "gid_t range covers all uint32_t values on this platform"; + } + + ::flatbuffers::FlatBufferBuilder fbb; + + auto sandbox = fb::CreateSandbox(fbb, + 1000 /*uid*/, + std::numeric_limits::max() /*gid*/, + 0 /*supplementary_group_ids*/, + 0 /*security_policy*/, + fb::SchedulingPolicy::OTHER, + 0 /*scheduling_priority*/); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "bad_gid_comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, SandboxSupplementaryGidOutOfRangeReturnsInvalidFormat) +{ + RecordProperty("Description", "A supplementary group id exceeding gid_t range returns InvalidFormat."); + + if constexpr (std::numeric_limits::max() >= std::numeric_limits::max()) + { + GTEST_SKIP() << "gid_t range covers all uint32_t values on this platform"; + } + + ::flatbuffers::FlatBufferBuilder fbb; + + auto supp_gids = fbb.CreateVector(std::vector{100, std::numeric_limits::max()}); + auto sandbox = fb::CreateSandbox(fbb, + 1000 /*uid*/, + 1000 /*gid*/, + supp_gids, + 0 /*security_policy*/, + fb::SchedulingPolicy::OTHER, + 0 /*scheduling_priority*/); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "bad_supp_gid_comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +// ============================================================================ +// Missing required field tests +// ============================================================================ + +TEST_F(FlatbufferConfigLoaderTest, MissingSchemaVersionReturnsInvalidFormat) +{ + RecordProperty("Description", "A config without schema_version returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto irt = fbb.CreateString("Startup"); + auto fallback = fb::CreateFallbackRunTarget(fbb, 0, 0, 1.0); + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto config = fb::CreateLaunchManagerConfig(fbb, std::nullopt, comps, rts, irt, fallback, alive_sup); + + auto result = loadBuffer(finishBuffer(fbb, config)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingEvaluationCycleReturnsInvalidFormat) +{ + RecordProperty("Description", "AliveSupervision without evaluation_cycle returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto irt = fbb.CreateString("Startup"); + auto fallback = fb::CreateFallbackRunTarget(fbb, 0, 0, 1.0); + auto alive_sup = fb::CreateAliveSupervision(fbb); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto config = fb::CreateLaunchManagerConfig(fbb, 1, comps, rts, irt, fallback, alive_sup); + + auto result = loadBuffer(finishBuffer(fbb, config)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingFallbackTransitionTimeoutReturnsInvalidFormat) +{ + RecordProperty("Description", "FallbackRunTarget without transition_timeout returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto irt = fbb.CreateString("Startup"); + auto fallback = fb::CreateFallbackRunTarget(fbb); + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto config = fb::CreateLaunchManagerConfig(fbb, 1, comps, rts, irt, fallback, alive_sup); + + auto result = loadBuffer(finishBuffer(fbb, config)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingRunTargetTransitionTimeoutReturnsInvalidFormat) +{ + RecordProperty("Description", "RunTarget without transition_timeout returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto switch_target = fbb.CreateString("SafeState"); + auto switch_action = fb::CreateSwitchRunTargetAction(fbb, switch_target); + auto rt_name = fbb.CreateString("Startup"); + auto rt = fb::CreateRunTarget(fbb, rt_name, 0, 0, std::nullopt, switch_action); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{rt}); + + auto result = loadBuffer(buildConfigWithRunTargets(fbb, rts)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingReadyTimeoutReturnsInvalidFormat) +{ + RecordProperty("Description", "DeploymentConfig without ready_timeout returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto sandbox = buildDefaultSandbox(fbb); + auto deploy = fb::CreateDeploymentConfig(fbb, std::nullopt, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingShutdownTimeoutReturnsInvalidFormat) +{ + RecordProperty("Description", "DeploymentConfig without shutdown_timeout returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto sandbox = buildDefaultSandbox(fbb); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, std::nullopt, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingApplicationTypeReturnsInvalidFormat) +{ + RecordProperty("Description", "ApplicationProfile without application_type returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto app_profile = fb::CreateApplicationProfile(fbb, std::nullopt, false); + auto bin_name = fbb.CreateString("bin"); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + auto comp_props = fb::CreateComponentProperties(fbb, bin_name, app_profile, 0, 0, ready_cond); + + auto comp = buildDefaultComponent(fbb, "comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingIsSelfTerminatingReturnsInvalidFormat) +{ + RecordProperty("Description", "ApplicationProfile without is_self_terminating returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, std::nullopt); + auto bin_name = fbb.CreateString("bin"); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + auto comp_props = fb::CreateComponentProperties(fbb, bin_name, app_profile, 0, 0, ready_cond); + + auto comp = buildDefaultComponent(fbb, "comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingProcessStateReturnsInvalidFormat) +{ + RecordProperty("Description", "ReadyCondition without process_state returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, false); + auto bin_name = fbb.CreateString("bin"); + auto ready_cond = fb::CreateReadyCondition(fbb); + auto comp_props = fb::CreateComponentProperties(fbb, bin_name, app_profile, 0, 0, ready_cond); + + auto comp = buildDefaultComponent(fbb, "comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingReportingCycleReturnsInvalidFormat) +{ + RecordProperty("Description", "ComponentAliveSupervision without reporting_cycle returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto comp_alive_sup = fb::CreateComponentAliveSupervision(fbb, std::nullopt, 3); + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, false, comp_alive_sup); + auto bin_name = fbb.CreateString("bin"); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + auto comp_props = fb::CreateComponentProperties(fbb, bin_name, app_profile, 0, 0, ready_cond); + + auto comp = buildDefaultComponent(fbb, "comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingFailedCyclesToleranceReturnsInvalidFormat) +{ + RecordProperty("Description", "ComponentAliveSupervision without failed_cycles_tolerance returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto comp_alive_sup = fb::CreateComponentAliveSupervision(fbb, 1.0, std::nullopt); + auto app_profile = fb::CreateApplicationProfile(fbb, fb::ApplicationType::Native, false, comp_alive_sup); + auto bin_name = fbb.CreateString("bin"); + auto ready_cond = fb::CreateReadyCondition(fbb, fb::ProcessState::Running); + auto comp_props = fb::CreateComponentProperties(fbb, bin_name, app_profile, 0, 0, ready_cond); + + auto comp = buildDefaultComponent(fbb, "comp", comp_props); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingRestartActionFieldsReturnsInvalidFormat) +{ + RecordProperty("Description", "RestartAction without required fields returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto restart = fb::CreateRestartAction(fbb); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto sandbox = buildDefaultSandbox(fbb); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, restart, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingSandboxUidReturnsInvalidFormat) +{ + RecordProperty("Description", "Sandbox without uid returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto sandbox = fb::CreateSandbox(fbb, std::nullopt, 0, 0, 0, fb::SchedulingPolicy::OTHER, 0); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingSandboxGidReturnsInvalidFormat) +{ + RecordProperty("Description", "Sandbox without gid returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto sandbox = fb::CreateSandbox(fbb, 0, std::nullopt, 0, 0, fb::SchedulingPolicy::OTHER, 0); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingSandboxSchedulingPolicyReturnsInvalidFormat) +{ + RecordProperty("Description", "Sandbox without scheduling_policy returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto sandbox = fb::CreateSandbox(fbb, 0, 0, 0, 0, std::nullopt, 0); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingSandboxSchedulingPriorityReturnsInvalidFormat) +{ + RecordProperty("Description", "Sandbox without scheduling_priority returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto sandbox = fb::CreateSandbox(fbb, 0, 0, 0, 0, fb::SchedulingPolicy::OTHER, std::nullopt); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingWatchdogMaxTimeoutReturnsInvalidFormat) +{ + RecordProperty("Description", "Watchdog without max_timeout returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto dev_path = fbb.CreateString("/dev/watchdog0"); + auto watchdog = fb::CreateWatchdog(fbb, dev_path, std::nullopt, true, false); + + auto fallback = fb::CreateFallbackRunTarget(fbb, 0, 0, 1.0); + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto irt = fbb.CreateString("Startup"); + auto config = fb::CreateLaunchManagerConfig(fbb, 1, comps, rts, irt, fallback, alive_sup, watchdog); + + auto result = loadBuffer(finishBuffer(fbb, config)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingWatchdogDeactivateOnShutdownReturnsInvalidFormat) +{ + RecordProperty("Description", "Watchdog without deactivate_on_shutdown returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto dev_path = fbb.CreateString("/dev/watchdog0"); + auto watchdog = fb::CreateWatchdog(fbb, dev_path, 30.0, std::nullopt, false); + + auto fallback = fb::CreateFallbackRunTarget(fbb, 0, 0, 1.0); + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto irt = fbb.CreateString("Startup"); + auto config = fb::CreateLaunchManagerConfig(fbb, 1, comps, rts, irt, fallback, alive_sup, watchdog); + + auto result = loadBuffer(finishBuffer(fbb, config)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +TEST_F(FlatbufferConfigLoaderTest, MissingWatchdogRequireMagicCloseReturnsInvalidFormat) +{ + RecordProperty("Description", "Watchdog without require_magic_close returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + auto dev_path = fbb.CreateString("/dev/watchdog0"); + auto watchdog = fb::CreateWatchdog(fbb, dev_path, 30.0, true, std::nullopt); + + auto fallback = fb::CreateFallbackRunTarget(fbb, 0, 0, 1.0); + auto alive_sup = fb::CreateAliveSupervision(fbb, 1.0); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto rts = fbb.CreateVector(std::vector<::flatbuffers::Offset>{}); + auto irt = fbb.CreateString("Startup"); + auto config = fb::CreateLaunchManagerConfig(fbb, 1, comps, rts, irt, fallback, alive_sup, watchdog); + + auto result = loadBuffer(finishBuffer(fbb, config)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +// ============================================================================ +// Parameterized scheduling policy tests +// ============================================================================ + +struct SchedulingPolicyTestParam +{ + fb::SchedulingPolicy fb_policy; + int32_t expected_posix_value; + const char* name; +}; + +class SchedulingPolicyTest : public FlatbufferConfigLoaderTest, + public ::testing::WithParamInterface +{ +}; + +TEST_P(SchedulingPolicyTest, ConvertsToExpectedPosixValue) +{ + RecordProperty("Description", "Scheduling policy enum maps to correct POSIX value."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto sandbox = fb::CreateSandbox(fbb, + 1000 /*uid*/, + 1000 /*gid*/, + 0 /*supplementary_group_ids*/, + 0 /*security_policy*/, + GetParam().fb_policy, + 0 /*scheduling_priority*/); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "sched_comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + EXPECT_THAT(result->components()[0].deployment_config.sandbox.scheduling_policy, + Eq(GetParam().expected_posix_value)); +} + +INSTANTIATE_TEST_SUITE_P(SchedulingPolicies, + SchedulingPolicyTest, + ::testing::Values(SchedulingPolicyTestParam{fb::SchedulingPolicy::FIFO, + SCHED_FIFO, + "SCHED_FIFO"}, + SchedulingPolicyTestParam{fb::SchedulingPolicy::RR, + SCHED_RR, + "SCHED_RR"}, + SchedulingPolicyTestParam{fb::SchedulingPolicy::OTHER, + SCHED_OTHER, + "SCHED_OTHER"}), + [](const ::testing::TestParamInfo& info) { + return info.param.name; + }); + +TEST_F(FlatbufferConfigLoaderTest, UnsupportedSchedulingPolicyReturnsInvalidFormat) +{ + RecordProperty("Description", "An unsupported scheduling policy value returns InvalidFormat."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto sandbox = fb::CreateSandbox(fbb, + 1000 /*uid*/, + 1000 /*gid*/, + 0 /*supplementary_group_ids*/, + 0 /*security_policy*/, + static_cast(99), + 0 /*scheduling_priority*/); + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig(fbb, 1.0, 1.0, 0, bin_dir, work_dir, 0, 0, sandbox); + + auto comp = buildDefaultComponent(fbb, "bad_sched_comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsFalse()); + EXPECT_THAT(result.error(), Eq(IConfigLoader::Error::InvalidFormat)); +} + +} // namespace +} // namespace score::launch_manager::config diff --git a/src/launch_manager_daemon/config/src/new_lm_flatcfg.fbs b/src/launch_manager_daemon/config/src/new_lm_flatcfg.fbs new file mode 100644 index 000000000..c491d4a10 --- /dev/null +++ b/src/launch_manager_daemon/config/src/new_lm_flatcfg.fbs @@ -0,0 +1,201 @@ +// FlatBuffers schema for S-CORE Launch Manager configuration. +// Equivalent to launch_manager.schema.json + +// All fields are commented as either required or optional +// For non-scalar values: +// - required values are defined with "(required)" keyword in the schema and it is verified by flatbuffer that the value has been configured +// - optional values may be nullptr and are populated in the code as std::optional +// For scalar values: +// - required values are defined with "= null" and it is verified when loading that they have been configured. +// Note: Without "= null", flatbuffer will return a default value and there is no way to know if it has been configured or not +// - optional values are defined with "= null" and they are populated as std::optional in the code + +namespace score.launch_manager.config.fb; + +// Specifies the level of integration between the component and the Launch Manager. +enum ApplicationType : byte { + Native = 0, + Reporting = 1, + Reporting_And_Supervised = 2, + State_Manager = 3 +} + +// Specifies the required state of the component's POSIX process. +enum ProcessState : byte { + Running = 0, + Terminated = 1 +} + +// Scheduling policy for a component's initial thread. +enum SchedulingPolicy : byte { + OTHER = 0, + FIFO = 1, + RR = 2 +} + +// Defines the configuration parameters used for alive monitoring of a component. +table ComponentAliveSupervision { + // Duration in seconds of the time interval used to verify alive notifications. + reporting_cycle:double = null; // required + // Maximum number of consecutive reporting cycle failures before recovery is triggered. + failed_cycles_tolerance:uint32 = null; // required + // Minimum number of checkpoints that must be reported within each reporting_cycle. + min_indications:uint32 = null; // optional + // Maximum number of checkpoints that may be reported within each reporting_cycle. + max_indications:uint32 = null; // optional +} + +// Defines the application profile that specifies the runtime behavior and capabilities of a component. +table ApplicationProfile { + application_type:ApplicationType = null; // required + // Whether the component terminates automatically once its tasks are completed. + is_self_terminating:bool = null; // required + alive_supervision:ComponentAliveSupervision; // optional +} + +// Defines the conditions that determine when the component enters the ready state. +table ReadyCondition { + process_state:ProcessState = null; // required +} + +// Defines essential characteristics of a software component. +table ComponentProperties { + // Relative path of the executable inside bin_dir. + binary_name:string (required); // required + application_profile:ApplicationProfile (required); // required + // Names of components that must reach ready state before this component starts. + depends_on:[string]; // optional + // Ordered list of command-line arguments passed to the component at startup. + process_arguments:[string]; // optional + ready_condition:ReadyCondition; // optional +} + +// Recovery action that restarts the POSIX process associated with a component. +table RestartAction { + // Maximum number of restart attempts before recovery is considered failed. + number_of_attempts:uint32 = null; // required + // Delay in seconds before initiating a restart attempt. + delay_before_restart:double = null; // required +} + +// Recovery action that switches to a specified Run Target. +table SwitchRunTargetAction { + // Name of the Run Target to switch to. + run_target:string (required); +} + +// A key-value pair representing an environment variable. +table EnvironmentalVariable { + key:string (required); + value:string (required); +} + +// Defines sandbox configuration parameters that isolate and constrain component execution. +table Sandbox { + // POSIX user ID (UID) under which the component executes. + uid:uint32 = null; // required + // Primary POSIX group ID (GID) under which the component executes. + gid:uint32 = null; // required + // Supplementary POSIX group IDs assigned to the component. + supplementary_group_ids:[uint32]; // optional + // Security policy or confinement profile name (e.g., SELinux or AppArmor profile). + security_policy:string; // optional + // Scheduling policy for the component's initial thread. + scheduling_policy:SchedulingPolicy = null; // required + // Scheduling priority for the component's initial thread. + scheduling_priority:int32 = null; // required + // Maximum memory in bytes the component is permitted to use. + max_memory_usage:uint64 = null; // optional + // Maximum CPU usage as a percentage of total CPU capacity. + max_cpu_usage:uint32 = null; // optional +} + +// Deployment configuration for a component. +table DeploymentConfig { + // Maximum time in seconds for the component to reach its ready state. + ready_timeout:double = null; // required + // Maximum time in seconds for the component to terminate after SIGTERM. + shutdown_timeout:double = null; // required + // Environment variables passed to the component at startup. + environmental_variables:[EnvironmentalVariable]; // optional + // Absolute path to the directory where the component is installed. + bin_dir:string (required); // required + // Working directory for the component during execution. + working_dir:string (required); // required + // Recovery action when the component fails to reach ready state. + // Can only be a RestartAction per the JSON schema constraint. + ready_recovery_action:RestartAction; // optional + // Recovery action when the component malfunctions after reaching ready state. + recovery_action:SwitchRunTargetAction; // optional + sandbox:Sandbox (required); // required +} + +// Configuration for a Run Target representing an operational mode of the system. +table RunTarget { + // Unique Run Target identifier. + name:string (required); + // Human-readable description of the Run Target. + description:string; // optional + // Names of components and Run Targets that must be activated with this Run Target. + depends_on:[string]; // optional + // Time limit in seconds for the Run Target transition. + transition_timeout:double = null; // required + // Recovery action when a component in this Run Target fails. + recovery_action:SwitchRunTargetAction (required); +} + +// An individual component entry with its identifier. +table Component { + // Unique component identifier. + name:string (required); // required + // Human-readable description of the component's purpose. + description:string; // optional + component_properties:ComponentProperties (required); // required + deployment_config:DeploymentConfig (required); // required +} + +// Global alive supervision configuration. +table AliveSupervision { + // Length in seconds of the time window used to assess alive supervision reports. + evaluation_cycle:double = null; // required +} + +// External watchdog device configuration. +table Watchdog { + // Path to the external watchdog device file (e.g., /dev/watchdog). + device_file_path:string (required); // required + // Maximum timeout in seconds configured on the external watchdog. + max_timeout:double = null; // required + // Whether the watchdog is deactivated during shutdown. + deactivate_on_shutdown:bool = null; // required + // Whether the magic close sequence is performed on intentional shutdown. + require_magic_close:bool = null; // required +} + +// Fallback Run Target activated when all recovery attempts are exhausted. +// Does not include a recovery_action. +table FallbackRunTarget { + // Human-readable description of the fallback Run Target. + description:string; // optional + // Names of components and Run Targets that must be activated. + depends_on:[string]; // optional + // Time limit in seconds for the Run Target transition. + transition_timeout:double = null; // required +} + +// Root configuration table for the S-CORE Launch Manager. +table LaunchManagerConfig { + // Schema version number. + schema_version:int32 = null; // required + // Software components managed by the Launch Manager. + components:[Component] (required); // required + // Run Targets representing different operational modes. + run_targets:[RunTarget] (required); // required + // Name of the initial Run Target activated during startup. + initial_run_target:string (required); // required + fallback_run_target:FallbackRunTarget (required); // required + alive_supervision:AliveSupervision (required); // required + watchdog:Watchdog; // optional +} + +root_type LaunchManagerConfig; diff --git a/src/launch_manager_daemon/health_monitor_lib/config/hm_flatcfg_generated.h b/src/launch_manager_daemon/health_monitor_lib/config/hm_flatcfg_generated.h index fab01f69b..528648266 100644 --- a/src/launch_manager_daemon/health_monitor_lib/config/hm_flatcfg_generated.h +++ b/src/launch_manager_daemon/health_monitor_lib/config/hm_flatcfg_generated.h @@ -21,8 +21,8 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && - FLATBUFFERS_VERSION_MINOR == 9 && - FLATBUFFERS_VERSION_REVISION == 23, + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, "Non-compatible flatbuffers version included"); namespace HMFlatBuffer { @@ -145,7 +145,8 @@ struct HMEcuCfg FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::Vector<::flatbuffers::Offset> *hmRecoveryNotification() const { return GetPointer> *>(VT_HMRECOVERYNOTIFICATION); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_VERSIONMAJOR, 4) && VerifyField(verifier, VT_VERSIONMINOR, 4) && @@ -299,7 +300,8 @@ struct Process FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::Vector<::flatbuffers::Offset> *processExecutionErrors() const { return GetPointer> *>(VT_PROCESSEXECUTIONERRORS); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_SHORTNAME) && verifier.VerifyString(shortName()) && @@ -398,7 +400,8 @@ struct HmProcessExecutionError FLATBUFFERS_FINAL_CLASS : private ::flatbuffers:: uint32_t processExecutionError() const { return GetField(VT_PROCESSEXECUTIONERROR, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_PROCESSEXECUTIONERROR, 4) && verifier.EndTable(); @@ -439,7 +442,8 @@ struct HmRefProcessGroupStates FLATBUFFERS_FINAL_CLASS : private ::flatbuffers:: const ::flatbuffers::String *identifier() const { return GetPointer(VT_IDENTIFIER); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_IDENTIFIER) && verifier.VerifyString(identifier()) && @@ -490,7 +494,8 @@ struct HmRefProcess FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint32_t index() const { return GetField(VT_INDEX, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_INDEX, 4) && verifier.EndTable(); @@ -551,7 +556,8 @@ struct HmMonitorInterface FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table int32_t permittedUid() const { return GetField(VT_PERMITTEDUID, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_INSTANCESPECIFIER) && verifier.VerifyString(instanceSpecifier()) && @@ -656,7 +662,8 @@ struct HmSupervisionCheckpoint FLATBUFFERS_FINAL_CLASS : private ::flatbuffers:: uint32_t refInterfaceIndex() const { return GetField(VT_REFINTERFACEINDEX, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_SHORTNAME) && verifier.VerifyString(shortName()) && @@ -735,7 +742,8 @@ struct HmCheckpointTransition FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::T const HMFlatBuffer::HmSupervisionCheckpoint *infoTarget() const { return GetPointer(VT_INFOTARGET); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_REFSOURCECPINDEX, 4) && VerifyField(verifier, VT_REFTARGETCPINDEX, 4) && @@ -832,7 +840,8 @@ struct HmAliveSupervision FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table const ::flatbuffers::Vector<::flatbuffers::Offset> *refProcessGroupStates() const { return GetPointer> *>(VT_REFPROCESSGROUPSTATES); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_RULECONTEXTKEY) && verifier.VerifyString(ruleContextKey()) && @@ -966,7 +975,8 @@ struct HmLocalSupervision FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table const ::flatbuffers::Vector<::flatbuffers::Offset> *hmRefAliveSupervision() const { return GetPointer> *>(VT_HMREFALIVESUPERVISION); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_RULECONTEXTKEY) && verifier.VerifyString(ruleContextKey()) && @@ -1042,7 +1052,8 @@ struct HmRefAliveSupervision FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Ta uint32_t refAliveSupervisionIdx() const { return GetField(VT_REFALIVESUPERVISIONIDX, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_RULECONTEXTKEY, 4) && VerifyField(verifier, VT_REFALIVESUPERVISIONIDX, 4) && @@ -1105,7 +1116,8 @@ struct HmGlobalSupervision FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Tabl const ::flatbuffers::Vector<::flatbuffers::Offset> *refProcessGroupStates() const { return GetPointer> *>(VT_REFPROCESSGROUPSTATES); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_RULECONTEXTKEY) && verifier.VerifyString(ruleContextKey()) && @@ -1197,7 +1209,8 @@ struct HmGlobalSupervisionLocalRef FLATBUFFERS_FINAL_CLASS : private ::flatbuffe uint32_t refLocalSupervisionIndex() const { return GetField(VT_REFLOCALSUPERVISIONINDEX, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_REFLOCALSUPERVISIONINDEX, 4) && verifier.EndTable(); @@ -1242,7 +1255,8 @@ struct HmRefProcessGroupStatesGlobal FLATBUFFERS_FINAL_CLASS : private ::flatbuf double expiredSupervisionTolerance() const { return GetField(VT_EXPIREDSUPERVISIONTOLERANCE, 0.0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_IDENTIFIER) && verifier.VerifyString(identifier()) && @@ -1313,7 +1327,8 @@ struct RecoveryNotification FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Tab bool shouldFireWatchdog() const { return GetField(VT_SHOULDFIREWATCHDOG, 0) != 0; } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffsetRequired(verifier, VT_SHORTNAME) && verifier.VerifyString(shortName()) && @@ -1405,14 +1420,16 @@ inline bool SizePrefixedHMEcuCfgBufferHasIdentifier(const void *buf) { buf, HMEcuCfgIdentifier(), true); } +template inline bool VerifyHMEcuCfgBuffer( - ::flatbuffers::Verifier &verifier) { - return verifier.VerifyBuffer(HMEcuCfgIdentifier()); + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(HMEcuCfgIdentifier()); } +template inline bool VerifySizePrefixedHMEcuCfgBuffer( - ::flatbuffers::Verifier &verifier) { - return verifier.VerifySizePrefixedBuffer(HMEcuCfgIdentifier()); + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(HMEcuCfgIdentifier()); } inline const char *HMEcuCfgExtension() { diff --git a/src/launch_manager_daemon/health_monitor_lib/config/hmcore_flatcfg_generated.h b/src/launch_manager_daemon/health_monitor_lib/config/hmcore_flatcfg_generated.h index 4d3fb96d6..8b27af346 100644 --- a/src/launch_manager_daemon/health_monitor_lib/config/hmcore_flatcfg_generated.h +++ b/src/launch_manager_daemon/health_monitor_lib/config/hmcore_flatcfg_generated.h @@ -21,8 +21,8 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && - FLATBUFFERS_VERSION_MINOR == 9 && - FLATBUFFERS_VERSION_REVISION == 23, + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, "Non-compatible flatbuffers version included"); namespace HMCOREFlatBuffer { @@ -56,7 +56,8 @@ struct HMCOREEcuCfg FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::Vector<::flatbuffers::Offset> *config() const { return GetPointer> *>(VT_CONFIG); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_VERSIONMAJOR, 4) && VerifyField(verifier, VT_VERSIONMINOR, 4) && @@ -159,7 +160,8 @@ struct Watchdog FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { bool hasValueRequireMagicClose() const { return GetField(VT_HASVALUEREQUIREMAGICCLOSE, 0) != 0; } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_SHORTNAME) && verifier.VerifyString(shortName()) && @@ -276,7 +278,8 @@ struct HmConfig FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint16_t bufferSizeGlobalSupervision() const { return GetField(VT_BUFFERSIZEGLOBALSUPERVISION, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_PERIODICITY, 4) && VerifyField(verifier, VT_BUFFERSIZEMONITOR, 2) && @@ -355,14 +358,16 @@ inline bool SizePrefixedHMCOREEcuCfgBufferHasIdentifier(const void *buf) { buf, HMCOREEcuCfgIdentifier(), true); } +template inline bool VerifyHMCOREEcuCfgBuffer( - ::flatbuffers::Verifier &verifier) { - return verifier.VerifyBuffer(HMCOREEcuCfgIdentifier()); + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(HMCOREEcuCfgIdentifier()); } +template inline bool VerifySizePrefixedHMCOREEcuCfgBuffer( - ::flatbuffers::Verifier &verifier) { - return verifier.VerifySizePrefixedBuffer(HMCOREEcuCfgIdentifier()); + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(HMCOREEcuCfgIdentifier()); } inline const char *HMCOREEcuCfgExtension() {