Skip to content

Commit

Permalink
Bugfix: fix mappings and global errors (#220)
Browse files Browse the repository at this point in the history
* Fix mappings of requirements not being transmitted to module
* 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
* Add missing mappings of module to serialized config

---------

Signed-off-by: Kai-Uwe Hermann <[email protected]>
  • Loading branch information
hikinggrass authored Dec 10, 2024
1 parent 72dec12 commit 6ff5d21
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 104 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
Expand Down
48 changes: 26 additions & 22 deletions include/utils/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,32 @@ class ConfigBase {
/// \returns the module config cache
std::unordered_map<std::string, ConfigCache> get_module_config_cache();

///
/// \return the cached mapping of module ids to module names
std::unordered_map<std::string, std::string> get_module_names();

///
/// \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<Requirement, Fulfillment> resolve_requirements(const std::string& module_id) const;

///
/// \returns a list of Requirements for \p module_id
std::list<Requirement> 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<std::string, std::vector<Fulfillment>> get_fulfillments(const std::string& module_id) const;

/// \returns the 3 tier model mappings
std::unordered_map<std::string, ModuleTierMappings> get_3_tier_model_mappings();

Expand Down Expand Up @@ -267,28 +293,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<Requirement, Fulfillment> resolve_requirements(const std::string& module_id) const;

///
/// \returns a list of Requirements for \p module_id
std::list<Requirement> 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<std::string, std::vector<Fulfillment>> 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
Expand Down
160 changes: 82 additions & 78 deletions lib/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,88 @@ std::unordered_map<std::string, ConfigCache> ConfigBase::get_module_config_cache
return this->module_config_cache;
}

std::unordered_map<std::string, std::string> 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();

// 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<Requirement, Fulfillment> ConfigBase::resolve_requirements(const std::string& module_id) const {
std::map<Requirement, Fulfillment> 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<Requirement> ConfigBase::get_requirements(const std::string& module_id) const {
BOOST_LOG_FUNCTION();

std::list<Requirement> res;

for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) {
res.push_back(requirement);
}

return res;
}

std::map<std::string, std::vector<Fulfillment>> ConfigBase::get_fulfillments(const std::string& module_id) const {
BOOST_LOG_FUNCTION();

std::map<std::string, std::vector<Fulfillment>> res;

for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) {
res[requirement.id].push_back(fulfillment);
}

return res;
}

std::unordered_map<std::string, ModuleTierMappings> ConfigBase::get_3_tier_model_mappings() {
return this->tier_mappings;
}
Expand Down Expand Up @@ -1054,84 +1136,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<Requirement, Fulfillment> Config::resolve_requirements(const std::string& module_id) const {
std::map<Requirement, Fulfillment> 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<Requirement> Config::get_requirements(const std::string& module_id) const {
BOOST_LOG_FUNCTION();

std::list<Requirement> res;

for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) {
res.push_back(requirement);
}

return res;
}

std::map<std::string, std::vector<Fulfillment>> Config::get_fulfillments(const std::string& module_id) const {
BOOST_LOG_FUNCTION();

std::map<std::string, std::vector<Fulfillment>> 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();

Expand Down
3 changes: 1 addition & 2 deletions lib/everest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
13 changes: 12 additions & 1 deletion src/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,21 @@ static std::map<pid_t, std::string> 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();
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();
}
}
}
// 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"] = json::object();
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
Expand Down

0 comments on commit 6ff5d21

Please sign in to comment.