From de1a66516c86a90490c9c4689066071ae8541a04 Mon Sep 17 00:00:00 2001 From: Kai-Uwe Hermann Date: Fri, 6 Dec 2024 20:17:22 +0100 Subject: [PATCH 1/3] Fix mappings of requirements not being transmitted to module Signed-off-by: Kai-Uwe Hermann --- CMakeLists.txt | 2 +- include/utils/config.hpp | 44 +++++------ lib/config.cpp | 156 +++++++++++++++++++-------------------- src/manager.cpp | 13 +++- 4 files changed, 110 insertions(+), 105 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 73003602..e3d0acae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) project(everest-framework - VERSION 0.19.0 + VERSION 0.19.1 DESCRIPTION "The open operating system for e-mobility charging stations" LANGUAGES CXX C ) diff --git a/include/utils/config.hpp b/include/utils/config.hpp index 9ee7e97e..34e2f43d 100644 --- a/include/utils/config.hpp +++ b/include/utils/config.hpp @@ -144,6 +144,28 @@ class ConfigBase { /// \returns the module config cache std::unordered_map get_module_config_cache(); + /// + /// \brief checks if the given \p module_id provides the requirement given in \p requirement_id + /// + /// \returns a json object that contains the requirement + nlohmann::json resolve_requirement(const std::string& module_id, const std::string& requirement_id) const; + + /// + /// \brief resolves all Requirements of the given \p module_id to their Fulfillments + /// + /// \returns a map indexed by Requirements + std::map resolve_requirements(const std::string& module_id) const; + + /// + /// \returns a list of Requirements for \p module_id + std::list get_requirements(const std::string& module_id) const; + + /// + /// \brief A Fulfillment is a combination of a Requirement and the module and implementation ids where this is + /// implemented + /// \returns a map of Fulfillments for \p module_id + std::map> get_fulfillments(const std::string& module_id) const; + /// \returns the 3 tier model mappings std::unordered_map get_3_tier_model_mappings(); @@ -267,28 +289,6 @@ class Config : public ConfigBase { /// \returns the commands that the modules \p module_name implements from the given implementation \p impl_id nlohmann::json get_module_cmds(const std::string& module_name, const std::string& impl_id); - /// - /// \brief checks if the given \p module_id provides the requirement given in \p requirement_id - /// - /// \returns a json object that contains the requirement - nlohmann::json resolve_requirement(const std::string& module_id, const std::string& requirement_id) const; - - /// - /// \brief resolves all Requirements of the given \p module_id to their Fulfillments - /// - /// \returns a map indexed by Requirements - std::map resolve_requirements(const std::string& module_id) const; - - /// - /// \returns a list of Requirements for \p module_id - std::list get_requirements(const std::string& module_id) const; - - /// - /// \brief A Fulfillment is a combination of a Requirement and the module and implementation ids where this is - /// implemented - /// \returns a map of Fulfillments for \p module_id - std::map> get_fulfillments(const std::string& module_id) const; - /// /// \brief A RequirementInitialization contains everything needed to initialize a requirement in user code. This /// includes the Requirement, its Fulfillment and an optional Mapping diff --git a/lib/config.cpp b/lib/config.cpp index 8fb25067..91fcd820 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -357,6 +357,84 @@ std::unordered_map ConfigBase::get_module_config_cache return this->module_config_cache; } +json ConfigBase::resolve_requirement(const std::string& module_id, const std::string& requirement_id) const { + BOOST_LOG_FUNCTION(); + + // FIXME (aw): this function should throw, if the requirement id + // isn't even listed in the module manifest + // FIXME (aw): the following if doesn't check for the requirement id + // at all + const auto module_name_it = this->module_names.find(module_id); + if (module_name_it == this->module_names.end()) { + EVLOG_AND_THROW(EverestApiError(fmt::format("Requested requirement id '{}' of module {} not found in config!", + requirement_id, printable_identifier(module_id)))); + } + + // check for connections for this requirement + const auto& module_config = this->main.at(module_id); + const std::string module_name = module_name_it->second; + const auto& requirement = this->manifests.at(module_name).at("requires").at(requirement_id); + if (!module_config.at("connections").contains(requirement_id)) { + return json::array(); // return an empty array if our config does not contain any connections for this + // requirement id + } + + // if only one single connection entry was required, return only this one + // callers can check with is_array() if this is a single connection (legacy) or a connection list + if (requirement.at("min_connections") == 1 && requirement.at("max_connections") == 1) { + return module_config.at("connections").at(requirement_id).at(0); + } + return module_config.at("connections").at(requirement_id); +} + +std::map ConfigBase::resolve_requirements(const std::string& module_id) const { + std::map requirements; + + const auto& module_name = get_module_name(module_id); + for (const auto& req_id : Config::keys(this->manifests.at(module_name).at("requires"))) { + const auto& resolved_req = this->resolve_requirement(module_id, req_id); + if (!resolved_req.is_array()) { + const auto& resolved_module_id = resolved_req.at("module_id"); + const auto& resolved_impl_id = resolved_req.at("implementation_id"); + const auto req = Requirement{req_id, 0}; + requirements[req] = {resolved_module_id, resolved_impl_id, req}; + } else { + for (std::size_t i = 0; i < resolved_req.size(); i++) { + const auto& resolved_module_id = resolved_req.at(i).at("module_id"); + const auto& resolved_impl_id = resolved_req.at(i).at("implementation_id"); + const auto req = Requirement{req_id, i}; + requirements[req] = {resolved_module_id, resolved_impl_id, req}; + } + } + } + + return requirements; +} + +std::list ConfigBase::get_requirements(const std::string& module_id) const { + BOOST_LOG_FUNCTION(); + + std::list res; + + for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) { + res.push_back(requirement); + } + + return res; +} + +std::map> ConfigBase::get_fulfillments(const std::string& module_id) const { + BOOST_LOG_FUNCTION(); + + std::map> res; + + for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) { + res[requirement.id].push_back(fulfillment); + } + + return res; +} + std::unordered_map ConfigBase::get_3_tier_model_mappings() { return this->tier_mappings; } @@ -1054,84 +1132,6 @@ json Config::get_module_cmds(const std::string& module_name, const std::string& return this->module_config_cache.at(module_name).cmds.at(impl_id); } -json Config::resolve_requirement(const std::string& module_id, const std::string& requirement_id) const { - BOOST_LOG_FUNCTION(); - - // FIXME (aw): this function should throw, if the requirement id - // isn't even listed in the module manifest - // FIXME (aw): the following if doesn't check for the requirement id - // at all - const auto module_name_it = this->module_names.find(module_id); - if (module_name_it == this->module_names.end()) { - EVLOG_AND_THROW(EverestApiError(fmt::format("Requested requirement id '{}' of module {} not found in config!", - requirement_id, printable_identifier(module_id)))); - } - - // check for connections for this requirement - const auto& module_config = this->main.at(module_id); - const std::string module_name = module_name_it->second; - const auto& requirement = this->manifests.at(module_name).at("requires").at(requirement_id); - if (!module_config.at("connections").contains(requirement_id)) { - return json::array(); // return an empty array if our config does not contain any connections for this - // requirement id - } - - // if only one single connection entry was required, return only this one - // callers can check with is_array() if this is a single connection (legacy) or a connection list - if (requirement.at("min_connections") == 1 && requirement.at("max_connections") == 1) { - return module_config.at("connections").at(requirement_id).at(0); - } - return module_config.at("connections").at(requirement_id); -} - -std::map Config::resolve_requirements(const std::string& module_id) const { - std::map requirements; - - const auto& module_name = get_module_name(module_id); - for (const auto& req_id : Config::keys(this->manifests.at(module_name).at("requires"))) { - const auto& resolved_req = this->resolve_requirement(module_id, req_id); - if (!resolved_req.is_array()) { - const auto& resolved_module_id = resolved_req.at("module_id"); - const auto& resolved_impl_id = resolved_req.at("implementation_id"); - const auto req = Requirement{req_id, 0}; - requirements[req] = {resolved_module_id, resolved_impl_id, req}; - } else { - for (std::size_t i = 0; i < resolved_req.size(); i++) { - const auto& resolved_module_id = resolved_req.at(i).at("module_id"); - const auto& resolved_impl_id = resolved_req.at(i).at("implementation_id"); - const auto req = Requirement{req_id, i}; - requirements[req] = {resolved_module_id, resolved_impl_id, req}; - } - } - } - - return requirements; -} - -std::list Config::get_requirements(const std::string& module_id) const { - BOOST_LOG_FUNCTION(); - - std::list res; - - for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) { - res.push_back(requirement); - } - - return res; -} - -std::map> Config::get_fulfillments(const std::string& module_id) const { - BOOST_LOG_FUNCTION(); - - std::map> res; - - for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) { - res[requirement.id].push_back(fulfillment); - } - - return res; -} - RequirementInitialization Config::get_requirement_initialization(const std::string& module_id) const { BOOST_LOG_FUNCTION(); diff --git a/src/manager.cpp b/src/manager.cpp index 1f7a04b2..94e600f6 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -357,10 +357,15 @@ static std::map start_modules(ManagerConfig& config, MQTTAbs json serialized_mod_config = serialized_config; serialized_mod_config["module_config"] = json::object(); serialized_mod_config["module_config"][module_name] = serialized_config.at("main").at(module_name); - const auto mappings = config.get_module_3_tier_model_mappings(module_name); - if (mappings.has_value()) { - serialized_mod_config["mappings"] = json::object(); - serialized_mod_config["mappings"][module_name] = mappings.value(); + const auto fulfillments = config.get_fulfillments(module_name); + serialized_mod_config["mappings"] = json::object(); + for (const auto& fulfillment_list : fulfillments) { + for (const auto& fulfillment : fulfillment_list.second) { + const auto mappings = config.get_module_3_tier_model_mappings(fulfillment.module_id); + if (mappings.has_value()) { + serialized_mod_config["mappings"][fulfillment.module_id] = mappings.value(); + } + } } serialized_mod_config.erase("main"); // FIXME: do not put this "main" config in there in the first place const auto telemetry_config = config.get_telemetry_config(module_name); From bffe3765788f30f8782f17dd8762d15668c9254d Mon Sep 17 00:00:00 2001 From: Kai-Uwe Hermann Date: Fri, 6 Dec 2024 20:18:22 +0100 Subject: [PATCH 2/3] Fix global error handling by iterating over the available module_names instead of the trimmed down main conig that doesn't have this information anymore Signed-off-by: Kai-Uwe Hermann --- include/utils/config.hpp | 4 ++++ lib/config.cpp | 4 ++++ lib/everest.cpp | 3 +-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/utils/config.hpp b/include/utils/config.hpp index 34e2f43d..0b9cf611 100644 --- a/include/utils/config.hpp +++ b/include/utils/config.hpp @@ -144,6 +144,10 @@ class ConfigBase { /// \returns the module config cache std::unordered_map get_module_config_cache(); + /// + /// \return the cached mapping of module ids to module names + std::unordered_map get_module_names(); + /// /// \brief checks if the given \p module_id provides the requirement given in \p requirement_id /// diff --git a/lib/config.cpp b/lib/config.cpp index 91fcd820..7a097dcf 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -357,6 +357,10 @@ std::unordered_map ConfigBase::get_module_config_cache return this->module_config_cache; } +std::unordered_map ConfigBase::get_module_names() { + return this->module_names; +} + json ConfigBase::resolve_requirement(const std::string& module_id, const std::string& requirement_id) const { BOOST_LOG_FUNCTION(); diff --git a/lib/everest.cpp b/lib/everest.cpp index 44344781..09aa0940 100644 --- a/lib/everest.cpp +++ b/lib/everest.cpp @@ -663,8 +663,7 @@ void Everest::subscribe_global_all_errors(const error::ErrorCallback& callback, clear_callback(error); }; - for (const std::string module_id : Config::keys(this->config.get_main_config())) { - const std::string module_name = this->config.get_module_name(module_id); + for (const auto& [module_id, module_name] : this->config.get_module_names()) { const json provides = this->config.get_manifests().at(module_name).at("provides"); for (const auto& impl : provides.items()) { const std::string impl_id = impl.key(); From ede95dafa270d3db94c258cbebd0faf9266a88f9 Mon Sep 17 00:00:00 2001 From: Kai-Uwe Hermann Date: Sun, 8 Dec 2024 17:22:54 +0100 Subject: [PATCH 3/3] Add missing mappings of module to serialized config Signed-off-by: Kai-Uwe Hermann --- src/manager.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/manager.cpp b/src/manager.cpp index 94e600f6..908a352f 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -356,6 +356,7 @@ static std::map start_modules(ManagerConfig& config, MQTTAbs const std::string module_name = module.key(); json serialized_mod_config = serialized_config; serialized_mod_config["module_config"] = json::object(); + // add mappings of fulfillments serialized_mod_config["module_config"][module_name] = serialized_config.at("main").at(module_name); const auto fulfillments = config.get_fulfillments(module_name); serialized_mod_config["mappings"] = json::object(); @@ -367,6 +368,11 @@ static std::map start_modules(ManagerConfig& config, MQTTAbs } } } + // also add mappings of module + const auto mappings = config.get_module_3_tier_model_mappings(module_name); + if (mappings.has_value()) { + serialized_mod_config["mappings"][module_name] = mappings.value(); + } serialized_mod_config.erase("main"); // FIXME: do not put this "main" config in there in the first place const auto telemetry_config = config.get_telemetry_config(module_name); if (telemetry_config.has_value()) {