diff --git a/score/mw/com/api_surface.lock.json b/score/mw/com/api_surface.lock.json index 61cc24708..28fd208fc 100644 --- a/score/mw/com/api_surface.lock.json +++ b/score/mw/com/api_surface.lock.json @@ -37,6 +37,18 @@ "kind": "function", "signature": "InitializeRuntime : void (const RuntimeConfiguration &)" }, + { + "name": "InitializeRuntimeAddonConfiguration", + "qualified_name": "score::mw::com::runtime::InitializeRuntimeAddonConfiguration", + "kind": "function", + "signature": "InitializeRuntimeAddonConfiguration : Result (const RuntimeConfiguration &)" + }, + { + "name": "InitializeRuntimeAddonConfiguration", + "qualified_name": "score::mw::com::runtime::InitializeRuntimeAddonConfiguration", + "kind": "function", + "signature": "InitializeRuntimeAddonConfiguration : Result (score::json::Any)" + }, { "name": "RuntimeConfiguration", "qualified_name": "score::mw::com::runtime::RuntimeConfiguration", diff --git a/score/mw/com/doc/user_facing_API_examples.md b/score/mw/com/doc/user_facing_API_examples.md index caea21437..f72832b15 100644 --- a/score/mw/com/doc/user_facing_API_examples.md +++ b/score/mw/com/doc/user_facing_API_examples.md @@ -16,6 +16,7 @@ This document contains examples of each mw::com user facing API. | [`RuntimeConfiguration(argc, argv)`](#example-3-using-runtimeconfiguration-for-configuration-management) | | [`RuntimeConfiguration(Path)`](#example-3-using-runtimeconfiguration-for-configuration-management) | | [`RuntimeConfiguration::GetConfigurationPath()`](#example-3-using-runtimeconfiguration-for-configuration-management) | +| [`RuntimeConfiguration::InitializeRuntimeAddonConfiguration()`](#example-4-using-initializeruntimeaddonconfiguration-to-load-additional-mwcom-configurations) | | **Data Types** | | [`InstanceIdentifier::Create()`](#example-1-using-instanceidentifier-for-service-instance-management) | | [`InstanceIdentifier::ToString()`](#example-1-using-instanceidentifier-for-service-instance-management) | @@ -274,6 +275,47 @@ const auto& config_path = default_config.GetConfigurationPath(); --- + +### Example 4: Using `InitializeRuntimeAddonConfiguration` to load additional `mw::com` configurations + +`Runtime` provides the APIs `InitializeRuntimeAddonConfiguration(RuntimeConfiguration&)` and `InitializeRuntimeAddonConfiguration(score::json::Any)` +to load additional configurations. For example, this can be used by libraries that also rely on mw::com to load their +configuration in addition to the application's configuration. It is assumed that prior to that call a complete mw::com configuration has been +loaded via `InitializeRuntime()`. If not this call will cause an application termination. +Add-on configurations will be merged into the existing configuration. Merge conflicts due to duplicate service type +and service instance definitions will lead to an application termination. Add-on configurations are not supposed to contain a `global configuration` +but only contains additional service types and service instances. +In all cases of application termination, the error message will indicate the reason for the termination. + +
+ +```cpp +#include "score/mw/com/runtime.h" + +int main(int argc, char* argv[]) { + // Initialize mw::com runtime with command line arguments + score::mw::com::runtime::InitializeRuntime(argc, argv); + + // Load additional add-on configuration + score::mw::com::runtime::RuntimeConfiguration addon_config{"/path/to/addon/mw_com_addon_config.json"}; + score::mw::com::runtime::InitializeAddOnConfiguration(addon_config); + + // If no error occurred the application will reach this point and the configuration will be updated + + return 0; +} +``` + +#### Key Points +- Configurations will be merged into the existing configuration +- If no (complete) configuration has been loaded so far, calling this function will cause an application termination. +- In case of merge conflicts, between the existing configuration and the add-on configuration, the application will terminate. +- Add-on configurations are not supposed to have a `global configuration` + +
+ +--- + ## `mw::com Data Type` API Examples: ### Example 1: Using `InstanceIdentifier` for Service Instance Management diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index cec1a9177..e462db5a8 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -1186,6 +1186,8 @@ cc_unit_test( "//score/mw/com/impl/configuration:mw_com_config_disabled_trace_config.json", "//score/mw/com/impl/configuration:mw_com_config_invalid_trace_config_path.json", "//score/mw/com/impl/configuration:mw_com_config_other.json", + "//score/mw/com/impl/configuration:mw_com_config_to_merge.json", + "//score/mw/com/impl/configuration:mw_com_config_to_merge_second.json", "//score/mw/com/impl/configuration:mw_com_config_valid_trace_config.json", "//score/mw/com/impl/tracing/configuration:comtrace_filter_config_small.json", ], diff --git a/score/mw/com/impl/configuration/BUILD b/score/mw/com/impl/configuration/BUILD index 6e61af468..331f38cfe 100644 --- a/score/mw/com/impl/configuration/BUILD +++ b/score/mw/com/impl/configuration/BUILD @@ -712,6 +712,18 @@ filegroup( visibility = ["//score/mw/com:__subpackages__"], ) +filegroup( + name = "mw_com_config_to_merge.json", + srcs = ["example/mw_com_config_to_merge.json"], + visibility = ["//score/mw/com/impl:__subpackages__"], +) + +filegroup( + name = "mw_com_config_to_merge_second.json", + srcs = ["example/mw_com_config_to_merge_second.json"], + visibility = ["//score/mw/com/impl:__subpackages__"], +) + filegroup( name = "mw_com_config_invalid_trace_config_path.json", srcs = ["example/mw_com_config_invalid_trace_config_path.json"], diff --git a/score/mw/com/impl/configuration/configuration.cpp b/score/mw/com/impl/configuration/configuration.cpp index 3b51a53f7..529b72b30 100644 --- a/score/mw/com/impl/configuration/configuration.cpp +++ b/score/mw/com/impl/configuration/configuration.cpp @@ -25,48 +25,174 @@ Configuration::Configuration(ServiceTypeDeployments service_types, ServiceInstanceDeployments service_instances, GlobalConfiguration global_configuration, TracingConfiguration tracing_configuration) noexcept - : service_types_{std::move(service_types)}, - service_instances_{std::move(service_instances)}, + : service_types_{std::make_shared>>()}, + service_instances_{std::make_shared>>()}, global_configuration_{std::move(global_configuration)}, tracing_configuration_{std::move(tracing_configuration)} +{ + service_types_->push_front(std::make_shared(std::move(service_types))); + service_instances_->push_front(std::make_shared(std::move(service_instances))); +} + +Configuration::Configuration(Configuration&& other) noexcept + // Use lock_guard to guard against a concurrent MergeServiceEntries() call on `other` racing with this move. + : Configuration(other, std::lock_guard(other.merge_mutex_)) +{ +} + +Configuration::Configuration(Configuration& other, const std::lock_guard&) noexcept + : service_types_{std::move(other.service_types_)}, + service_instances_{std::move(other.service_instances_)}, + global_configuration_{std::move(other.global_configuration_)}, + tracing_configuration_{std::move(other.tracing_configuration_)} +// merge_mutex_ itself is not movable and is left default-constructed in the new object. { } ServiceTypeDeployment* Configuration::AddServiceTypeDeployment(ServiceIdentifierType service_identifier_type, ServiceTypeDeployment service_type_deployment) noexcept { - const auto emplace_result = - service_types_.emplace(std::move(service_identifier_type), std::move(service_type_deployment)); - if (!emplace_result.second) + // If this service type has not yet been added (not in any map of the list), this service type will be added to the + // latest map of service types. + + const auto current_list = GetListOfServiceTypeMaps(); + + const bool type_found = CheckServiceTypeExists(service_identifier_type, current_list); + + if (!type_found) { - ::score::mw::log::LogFatal("lola") - << "Could not insert service type deployment into Configuration map. Terminating"; - std::terminate(); + const auto last_map_entry = current_list->back(); + const auto emplace_result = + last_map_entry->emplace(std::move(service_identifier_type), std::move(service_type_deployment)); + + if (emplace_result.second) + { + return &emplace_result.first->second; + } } - return &emplace_result.first->second; + + ::score::mw::log::LogFatal("lola") + << "Could not insert service type deployment into Configuration map. Terminating"; + std::terminate(); } ServiceInstanceDeployment* Configuration::AddServiceInstanceDeployments( InstanceSpecifier instance_specifier, ServiceInstanceDeployment service_instance_deployment) noexcept { - const auto emplace_result = - service_instances_.emplace(std::move(instance_specifier), std::move(service_instance_deployment)); - if (!emplace_result.second) + // If this service instance has not yet been added (not in any map of the list), this service instance will be added + // to the latest map of service instances. + + const auto current_list = GetListOfServiceInstanceMaps(); + + const bool instance_found = CheckServiceInstanceExists(instance_specifier, current_list); + + if (!instance_found) { - ::score::mw::log::LogFatal("lola") - << "Could not insert service instance deployment into Configuration map. Terminating"; - std::terminate(); + const auto last_map_entry = current_list->back(); + const auto emplace_result = + last_map_entry->emplace(std::move(instance_specifier), std::move(service_instance_deployment)); + + if (emplace_result.second) + { + return &emplace_result.first->second; + } } - return &emplace_result.first->second; + + ::score::mw::log::LogFatal("lola") + << "Could not insert service instance deployment into Configuration map. Terminating"; + std::terminate(); +} + +Result Configuration::MergeServiceEntries(const Configuration& additional_configuration) noexcept +{ + std::lock_guard lock(merge_mutex_); + + // Construct a new map of service type deployments and add all service_type_deployments of + // additional_configuration to it. Add this map as a front entry in the list of those entries, so that it will + // be returned as the latest version. + auto new_type_map = std::make_shared(); + bool new_type_element_inserted = false; + + const auto current_type_list = std::atomic_load_explicit(&service_types_, std::memory_order_acquire); + if (current_type_list != nullptr) + { + for (const auto& service_type : additional_configuration.GetServiceTypes()) + { + const bool type_found = CheckServiceTypeExists(service_type.first, current_type_list); + + if (!type_found) + { + new_type_map->emplace(service_type.first, service_type.second); + new_type_element_inserted = true; + } + else + { + return Unexpected(MakeError(configuration_errc::configuration_merge_duplicate_service_type)); + } + } + } + else + { + return Unexpected(MakeError(configuration_errc::configuration_merged_invalid_configuration_state)); + } + + if (new_type_element_inserted) + { + auto updated_list = std::make_shared>>(*service_types_); + updated_list->push_front(new_type_map); + + std::atomic_store_explicit(&service_types_, updated_list, std::memory_order_release); + } + + // Update service instances + + auto new_instance_map = std::make_shared(); + bool new_instance_element_inserted = false; + + const auto current_instance_list = std::atomic_load_explicit(&service_instances_, std::memory_order_acquire); + if (current_instance_list != nullptr) + { + for (const auto& service_instance : additional_configuration.GetServiceInstances()) + { + const bool instance_found = CheckServiceInstanceExists(service_instance.first, current_instance_list); + + if (!instance_found) + { + new_instance_map->emplace(service_instance.first, service_instance.second); + new_instance_element_inserted = true; + } + else + { + return Unexpected(MakeError(configuration_errc::configuration_merge_duplicate_service_instance)); + } + } + } + else + { + return Unexpected(MakeError(configuration_errc::configuration_merged_invalid_configuration_state)); + } + + if (new_instance_element_inserted) + { + auto updated_list = + std::make_shared>>(*service_instances_); + updated_list->push_front(new_instance_map); + + std::atomic_store_explicit(&service_instances_, updated_list, std::memory_order_release); + } + + return {}; } score::Result Configuration::Validate() const noexcept { + if (const auto result = CrossCheckAsilLevels(); !result.has_value()) { return result; } + if (const auto result = CrossCheckServiceInstancesToTypes(); !result.has_value()) { return result; @@ -76,7 +202,7 @@ score::Result Configuration::Validate() const noexcept score::Result Configuration::CrossCheckAsilLevels() const noexcept { - for (const auto& service_instance : service_instances_) + for (const auto& service_instance : GetServiceInstances()) { if ((service_instance.second.asilLevel_ == QualityType::kASIL_B) && (GetGlobalConfiguration().GetProcessAsilLevel() != QualityType::kASIL_B)) @@ -90,10 +216,11 @@ score::Result Configuration::CrossCheckAsilLevels() const noexcept score::Result Configuration::CrossCheckServiceInstancesToTypes() const noexcept { - for (const auto& service_instance : service_instances_) + for (const auto& service_instance : GetServiceInstances()) { - const auto foundServiceType = service_types_.find(service_instance.second.service_); - if (foundServiceType == service_types_.cend()) + const auto service_types = GetServiceTypes(); + const auto foundServiceType = service_types.find(service_instance.second.service_); + if (foundServiceType == service_types.cend()) { return MakeUnexpected( configuration_errc::configuration_invalid_type_reference_from_instance, @@ -152,7 +279,7 @@ score::Result Configuration::HasLolaServiceDeployment() const noexcept return false; }); - for (const auto& service_type : service_types_) + for (const auto& service_type : GetServiceTypes()) { if (std::visit(deployment_info_visitor, service_type.second.binding_info_)) { @@ -165,11 +292,10 @@ score::Result Configuration::HasLolaServiceDeployment() const noexcept std::set Configuration::GetServiceTypeNames() const noexcept { std::set configured_service_types{}; - for (const auto& map_entry : service_types_) - { + ForEachServiceType([&configured_service_types](const auto& map_entry) { const auto service_type_string_view = map_entry.first.ToString(); score::cpp::ignore = configured_service_types.insert(service_type_string_view); - } + }); return configured_service_types; } @@ -224,26 +350,20 @@ std::set Configuration::GetElementNamesOfServiceType(const std // LCOV_EXCL_STOP ); - // LCOV_EXCL_BR_START (Tool incorrectly marks the range-for loop as "Decision couldn't be analyzed". The false - // case (empty GetServiceTypes()) is structurally unreachable: GetElementNamesOfServiceType is only called from - // ParseEvents/ParseFields which are reached only after the service type was found in GetServiceTypes(). - // Suppression can be removed when the tooling bug is fixed.) - for (const auto& service_type_deployment : service_types_) - // LCOV_EXCL_BR_STOP - { + ForEachServiceType([&service_type, &service_type_deployment_visitor](const auto& service_type_deployment) { const ServiceIdentifierTypeView current_service_type_view{service_type_deployment.first}; if (current_service_type_view.getInternalTypeName() == service_type) { std::visit(service_type_deployment_visitor, service_type_deployment.second.binding_info_); } - } + }); return result; } std::set Configuration::GetAggregatedAllowedUsers(const QualityType asil_level) const noexcept { std::set aggregated_allowed_users{}; - for (const auto& instanceDeplElement : service_instances_) + for (const auto& instanceDeplElement : GetServiceInstances()) { const auto* const instance_deployment = std::get_if(&instanceDeplElement.second.bindingInfo_); @@ -290,16 +410,149 @@ bool Configuration::AggregateAllowedUsers(std::set& aggregated_allowed_us std::set Configuration::GetInstancesOfServiceType(std::string_view service_type) const noexcept { std::set result{}; - // LCOV_EXCL_BR_START (Tool incorrectly marks the range-for loop as "Decision couldn't be analyzed" despite all - // lines within the loop being covered. We also have a test for the case where GetServiceInstances() is empty. - // Suppression can be removed when the tooling bug is fixed.) - for (const auto& service_instance_element : service_instances_) - // LCOV_EXCL_BR_STOP - { + ForEachServiceInstance([&result, service_type](const auto& service_instance_element) { if (service_instance_element.second.service_.ToString() == service_type) { - const auto element_string_view = service_instance_element.first.ToString(); - score::cpp::ignore = result.insert(element_string_view); + score::cpp::ignore = result.insert(service_instance_element.first.ToString()); + } + }); + return result; +} + +std::optional> Configuration::GetServiceTypeDeployment( + const ServiceIdentifierType& service_identifier_type) const noexcept +{ + const auto current_list = std::atomic_load_explicit(&service_types_, std::memory_order_acquire); + if ((current_list == nullptr) || current_list->empty()) + { + return std::nullopt; + } + for (const auto& element : *current_list.get()) + { + const auto it = element->find(service_identifier_type); + if (it != element->end()) + { + return std::cref(it->second); + } + } + return std::nullopt; +} + +std::optional> Configuration::GetServiceInstanceDeployment( + const InstanceSpecifier& specifier) const noexcept +{ + const auto current_list = std::atomic_load_explicit(&service_instances_, std::memory_order_acquire); + if ((current_list == nullptr) || current_list->empty()) + { + return std::nullopt; + } + for (const auto& element : *current_list.get()) + { + const auto it = element->find(specifier); + if (it != element->end()) + { + return std::cref(it->second); + } + } + return std::nullopt; +} + +size_t Configuration::GetNumberOfServiceTypes() const noexcept +{ + const auto current_list = GetListOfServiceTypeMaps(); + size_t number_of_types = 0; + for (const auto& element : *current_list.get()) + { + number_of_types += element->size(); + } + return number_of_types; +} + +size_t Configuration::GetNumberOfServiceInstances() const noexcept +{ + const auto current_list = GetListOfServiceInstanceMaps(); + + size_t number_of_instances = 0; + for (const auto& element : *current_list.get()) + { + number_of_instances += element->size(); + } + return number_of_instances; +} + +bool Configuration::CheckServiceTypeExists( + const ServiceIdentifierType& service_identifier, + const std::shared_ptr>>>& + current_list) +{ + return std::any_of(current_list->begin(), current_list->end(), [&service_identifier](const auto& element) { + return element->find(service_identifier) != element->end(); + }); +} + +bool Configuration::CheckServiceInstanceExists( + const InstanceSpecifier& instance_identifier, + const std::shared_ptr>>>& + current_list) +{ + return std::any_of(current_list->begin(), current_list->end(), [&instance_identifier](const auto& element) { + return element->find(instance_identifier) != element->end(); + }); +} + +std::shared_ptr>> +Configuration::GetListOfServiceTypeMaps() const +{ + const auto current_list = std::atomic_load_explicit(&service_types_, std::memory_order_acquire); + if (current_list != nullptr && !current_list->empty()) + { + return current_list; + } + ::score::mw::log::LogFatal("lola") + << "Could not access Configuration's list of service type deployments. Terminating"; + std::terminate(); +} + +std::shared_ptr>> +Configuration::GetListOfServiceInstanceMaps() const +{ + const auto current_list = std::atomic_load_explicit(&service_instances_, std::memory_order_acquire); + if (current_list != nullptr && !current_list->empty()) + { + return current_list; + } + ::score::mw::log::LogFatal("lola") + << "Could not access Configuration's list of service instance deployments. Terminating"; + std::terminate(); +} + +Configuration::ServiceTypeDeployments Configuration::GetServiceTypes() const noexcept +{ + ServiceTypeDeployments result{}; + + const auto current_list = GetListOfServiceTypeMaps(); + + for (const auto& element : *current_list.get()) + { + for (const auto& entry : *element.get()) + { + result.emplace(entry.first, entry.second); + } + } + return result; +} + +Configuration::ServiceInstanceDeployments Configuration::GetServiceInstances() const noexcept +{ + ServiceInstanceDeployments result{}; + + const auto current_list = GetListOfServiceInstanceMaps(); + + for (const auto& element : *current_list.get()) + { + for (const auto& entry : *element.get()) + { + result.emplace(entry.first, entry.second); } } return result; diff --git a/score/mw/com/impl/configuration/configuration.h b/score/mw/com/impl/configuration/configuration.h index 02db78195..bd38ffa39 100644 --- a/score/mw/com/impl/configuration/configuration.h +++ b/score/mw/com/impl/configuration/configuration.h @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include #include @@ -53,6 +55,11 @@ class Configuration final using ServiceInstanceDeployments = std::unordered_map; using BindingInformation = std::variant; + // Suppress "AUTOSAR C++14 A11-3-1", The rule declares: "Friend declarations shall not be used". + // Test only use to check internal state of configuration after (merge) operations. + // coverity[autosar_cpp14_a11_3_1_violation] + friend class ConfigurationFixture; + Configuration(ServiceTypeDeployments service_types, ServiceInstanceDeployments service_instances, GlobalConfiguration global_configuration, @@ -63,10 +70,16 @@ class Configuration final * \brief Class is moveable but not copyable */ Configuration(const Configuration& other) = delete; - Configuration(Configuration&& other) noexcept = default; + Configuration(Configuration&& other) noexcept; Configuration& operator=(const Configuration& other) & = delete; Configuration& operator=(Configuration&& other) & = delete; + private: + /// \brief Delegate target used by the move constructor to hold `other.merge_mutex_` locked while the member-wise + /// moves happen, preventing race conditions with a concurrent 'write operations' on `other`. + Configuration(Configuration& other, const std::lock_guard&) noexcept; + + public: ServiceTypeDeployment* AddServiceTypeDeployment(ServiceIdentifierType service_identifier_type, ServiceTypeDeployment service_type_deployment) noexcept; ServiceInstanceDeployment* AddServiceInstanceDeployments( @@ -83,45 +96,29 @@ class Configuration final } std::optional> GetServiceTypeDeployment( - const ServiceIdentifierType& service_identifier_type) const noexcept - { - const auto it = service_types_.find(service_identifier_type); - if (it == service_types_.end()) - { - return std::nullopt; - } - return std::cref(it->second); - } + const ServiceIdentifierType& service_identifier_type) const noexcept; std::optional> GetServiceInstanceDeployment( - const InstanceSpecifier& specifier) const noexcept - { - const auto it = service_instances_.find(specifier); - if (it == service_instances_.end()) - { - return std::nullopt; - } - return std::cref(it->second); - } + const InstanceSpecifier& specifier) const noexcept; - size_t GetNumberOfServiceTypes() const noexcept - { - return service_types_.size(); - } + /// \brief Merge service types and instances into this configuration. + /// Checks for clashes in type names and instance names and will return error in this case. + /// \attention In case that the merge fails, the configuration will be left in an undefined state and should not be + /// used anymore. + Result MergeServiceEntries(const Configuration& additional_configuration) noexcept; + + size_t GetNumberOfServiceTypes() const noexcept; bool IsServiceTypesEmpty() const noexcept { - return service_types_.empty(); + return GetNumberOfServiceTypes() == 0; } - size_t GetNumberOfServiceInstances() const noexcept - { - return service_instances_.size(); - } + size_t GetNumberOfServiceInstances() const noexcept; bool IsServiceInstancesEmpty() const noexcept { - return service_instances_.empty(); + return GetNumberOfServiceInstances() == 0; } /// \brief Public interface to trigger a validation of this configuration. @@ -156,6 +153,84 @@ class Configuration final std::set GetInstancesOfServiceType(std::string_view service_type) const noexcept; private: + /// \brief Returns a copy of the merged entries across all persisted maps of service type deployments. + /// \attention This returns an owned copy, so no references, pointers or string_views should be extracted and used + /// beyond the copy's lifetime. For that use case, use ForEachServiceType() instead, which iterates directly + /// over the persisted storage. + ServiceTypeDeployments GetServiceTypes() const noexcept; + + /// \brief Returns a copy of the merged entries across all persisted maps of service isntance deployments. + /// \attention This returns an owned copy, so no references, pointers or string_views should be extracted and used + /// beyond the copy's lifetime. For that use case, use ForEachServiceInstance() instead, which iterates + /// directly over the persisted storage. + ServiceInstanceDeployments GetServiceInstances() const noexcept; + + /// \brief Invokes a callback with a const reference to every service type deployment entry + /// held across all persisted maps of service type deployments. + /// \attention This function was introduced to have a copy free access to the entries in those maps. This makes it + /// safe to extract references/string_views from the entries passed to callback and use them beyond + /// the lifetime of a single call, as long as this Configuration outlives them. + template + void ForEachServiceType(Callback&& callback) const noexcept + { + const auto current_list = std::atomic_load_explicit(&service_types_, std::memory_order_acquire); + if (current_list == nullptr) + { + return; + } + for (const auto& element : *current_list) + { + for (const auto& entry : *element) + { + callback(entry); + } + } + } + + /// \brief Invokes a callback with a const reference to every service instance deployment entry + /// held across all persisted maps of service instance deployments. + /// \attention Same lifetime guarantees as ForEachServiceType() above, but for service instance deployments. + template + void ForEachServiceInstance(Callback&& callback) const noexcept + { + const auto current_list = std::atomic_load_explicit(&service_instances_, std::memory_order_acquire); + if (current_list == nullptr) + { + return; + } + for (const auto& element : *current_list) + { + for (const auto& entry : *element) + { + callback(entry); + } + } + } + + /// \brief Get list of maps of service types. If list is not defined or empty, this function will terminate the + /// program. + std::shared_ptr>> GetListOfServiceTypeMaps() const; + + /// \brief Get list of maps of service instances. If list is not defined or empty, this function will terminate the + /// program. + std::shared_ptr>> GetListOfServiceInstanceMaps() const; + + /// \brief Helper function to check if entry for this service_identifier is already stored in list of service type + /// deployments + static bool CheckServiceTypeExists( + const ServiceIdentifierType& service_identifier, + const std::shared_ptr< + std::list>>>& + current_list); + + /// \brief Helper function to check if entry for this instance_identifier is already stored in list of instance + /// deployments + static bool CheckServiceInstanceExists( + const InstanceSpecifier& instance_identifier, + const std::shared_ptr< + std::list>>>& + current_list); + /// \brief Validate if service ASIL levels match the application's assigned ASIL level. score::Result CrossCheckAsilLevels() const noexcept; /// \brief Validate if service type definitions and service instance definitions fit together. @@ -172,15 +247,35 @@ class Configuration final const QualityType asil_level) noexcept; /** - * @brief map containing all the configured ports/InstanceSpecifiers for an executable. + * @brief List of all generations of the map containing the configured service type deployments. + * + * Key is the ServiceIdentifierType, value is the ServiceTypeDeployment. * - * Key is the string representation of the InstanceSpecifier aka port name. - * Value is the ServiceIdentifierType, the port is typed with. + * Each write update, i.e. AddServiceTypeDeployment() and MergeServiceEntries() will create a new map + * that will be added to this list. The different versions are stored in a list so that the pointers to the elements + * in it, stay valid for the lifetime of this configuration. Stored as std::shared_ptr so that atomic updates can be + * done to the list of maps, while the previous generations are still accessible by readers. */ - ServiceTypeDeployments service_types_; - ServiceInstanceDeployments service_instances_; + std::shared_ptr>> service_types_; + + /** + * @brief List of all generations of the map containing the configured service instance deployments. + * + * Key is the InstanceSpecifier, value is the ServiceInstanceDeployment. + * + * Each write update, i.e. AddServiceInstanceDeployment() and MergeServiceEntries() will create a new map + * that will be added to this list. The different versions are stored in a list so that the pointers to the elements + * in it, stay valid for the lifetime of this configuration. Stored as std::shared_ptr so that atomic updates can be + * done to the list of maps, while the previous generations are still accessible by readers. + */ + std::shared_ptr>> service_instances_; GlobalConfiguration global_configuration_; TracingConfiguration tracing_configuration_; + + /// This mutex is only used to avoid multiple simultaneous merges, i.e. calls of MergeServiceEntries(). On the read + /// path the usage of this mutex can be avoided because the list of configuration elements will be loaded via atomic + /// operations. + std::mutex merge_mutex_; }; } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/configuration/configuration_error.h b/score/mw/com/impl/configuration/configuration_error.h index 986381b87..6e94148a9 100644 --- a/score/mw/com/impl/configuration/configuration_error.h +++ b/score/mw/com/impl/configuration/configuration_error.h @@ -33,6 +33,9 @@ enum class configuration_errc : score::result::ErrorCode configuration_unsupported_type_binding = 6, configuration_invalid_event_reference_from_instance = 7, configuration_invalid_field_reference_from_instance = 8, + configuration_merge_duplicate_service_type = 9, + configuration_merge_duplicate_service_instance = 10, + configuration_merged_invalid_configuration_state = 11, }; /// \brief See above explanation in configuration_errc @@ -88,7 +91,17 @@ class ConfigurationErrorDomain final : public score::result::ErrorDomain configuration_errc::configuration_invalid_field_reference_from_instance): return "Service instance refers to a field, which doesn't exist in the referenced service type. This " "is invalid, terminating"; - + // coverity[autosar_cpp14_m6_4_5_violation] + case static_cast(configuration_errc::configuration_merge_duplicate_service_type): + return "Duplicate service type was found during configuration merge. Merge aborted."; + // coverity[autosar_cpp14_m6_4_5_violation] + case static_cast( + configuration_errc::configuration_merge_duplicate_service_instance): + return "Duplicate service instance was found during configuration merge. Merge aborted."; + // coverity[autosar_cpp14_m6_4_5_violation] + case static_cast( + configuration_errc::configuration_merged_invalid_configuration_state): + return "Configuration is an invalid state and merge cannot be performed."; // coverity[autosar_cpp14_m6_4_5_violation] default: return "unknown configuration error"; diff --git a/score/mw/com/impl/configuration/configuration_error_test.cpp b/score/mw/com/impl/configuration/configuration_error_test.cpp index 552ae9fd2..bc2c8c7a3 100644 --- a/score/mw/com/impl/configuration/configuration_error_test.cpp +++ b/score/mw/com/impl/configuration/configuration_error_test.cpp @@ -50,6 +50,24 @@ TEST_F(ConfigurationErrorTest, MessageForSerializationShmbindinginformationInval "serialization of is invalid"); } +TEST_F(ConfigurationErrorTest, MergeDuplicateServiceType) +{ + testErrorMessage(configuration_errc::configuration_merge_duplicate_service_type, + "Duplicate service type was found during configuration merge. Merge aborted."); +} + +TEST_F(ConfigurationErrorTest, MergeDuplicateServiceInstance) +{ + testErrorMessage(configuration_errc::configuration_merge_duplicate_service_instance, + "Duplicate service instance was found during configuration merge. Merge aborted."); +} + +TEST_F(ConfigurationErrorTest, MergeInvalidConfigurationState) +{ + testErrorMessage(configuration_errc::configuration_merged_invalid_configuration_state, + "Configuration is an invalid state and merge cannot be performed."); +} + TEST_F(ConfigurationErrorTest, MessageForDefault) { testErrorMessage(static_cast(-1), "unknown configuration error"); diff --git a/score/mw/com/impl/configuration/configuration_json_parsing_strategy.cpp b/score/mw/com/impl/configuration/configuration_json_parsing_strategy.cpp index cbbd5bb77..5308f7ed5 100644 --- a/score/mw/com/impl/configuration/configuration_json_parsing_strategy.cpp +++ b/score/mw/com/impl/configuration/configuration_json_parsing_strategy.cpp @@ -1244,14 +1244,6 @@ Configuration ConfigurationJsonParsingStrategy::Parse(score::json::Any json) con std::move(global_configuration), std::move(tracing_configuration)}; - const auto validation_result = configuration.Validate(); - - if (!validation_result.has_value()) - { - ::score::mw::log::LogFatal("lola") << validation_result.error().UserMessage(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(false); - } - return configuration; } diff --git a/score/mw/com/impl/configuration/configuration_json_parsing_strategy_test.cpp b/score/mw/com/impl/configuration/configuration_json_parsing_strategy_test.cpp index 39d86b8f0..d03dbb2ce 100644 --- a/score/mw/com/impl/configuration/configuration_json_parsing_strategy_test.cpp +++ b/score/mw/com/impl/configuration/configuration_json_parsing_strategy_test.cpp @@ -752,113 +752,6 @@ TEST_F(ConfigurationJsonParsingStrategyFixture, NoInstanceSpecifierInInstanceWil score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2))); } -TEST_F(ConfigurationJsonParsingStrategyFixture, ServiceInstanceReferencesUnknownServiceTypeWillDie) -{ - // Given a JSON, where a service instance references via serviceTypeName an unknown/not configured service type. - auto j2 = R"( - { - "serviceTypes": [ - { - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "bindings": [] - } - ], - "serviceInstances": [ - { - "instanceSpecifier": "abc/abc/TirePressurePort", - "serviceTypeName": "/score/ncar/services/MeDoesntExist", - "version": { - "major": 12, - "minor": 34 - }, - "instances": [ - { - "asil-level": "QM", - "binding": "SHM" - } - ] - } - ] - } -)"_json; - - // When parsing the JSON - // That the application will terminate - SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( - score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2))); -} - -TEST_F(ConfigurationJsonParsingStrategyFixture, ServiceInstanceEventReferencesUnknownServiceTypeEventWillDie) -{ - // Given a JSON, where a service instance event has a name, which doesn't exist in the serviceType it references. - auto j2 = R"( - { - "serviceTypes": [ - { - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "bindings": [ - { - "binding": "SHM", - "serviceId": 1234, - "events": [ - { - "eventName": "CurrentPressureFrontLeft", - "eventId": 20 - } - ], - "fields": [ - { - "fieldName": "CurrentTemperatureFrontLeft", - "fieldId": 30 - } - ] - } - ] - } - ], - "serviceInstances": [ - { - "instanceSpecifier": "abc/abc/TirePressurePort", - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "instances": [ - { - "instanceId": 1234, - "asil-level": "QM", - "binding": "SHM", - "events": [ - { - "eventName": "CurrentPressureFrontLeft" - }, - { - "eventName": "Unknown" - } - ], - "fields": [] - } - ] - } - ] - } -)"_json; - - // When parsing the JSON - // That the application will terminate - SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( - score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2))); -} - TEST_F(ConfigurationJsonParsingStrategyFixture, NoVersionInInstanceWillDie) { // Given a JSON without necessary attribute `version` @@ -2688,50 +2581,12 @@ TEST_P(InvalidProcessAsil, DieOnInvalidAsil) DISABLE_WARNING_POP } -std::string inconsistent_asil_config = R"json( - { - "serviceTypes": [ - { - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "bindings": [] - } - ], - "serviceInstances": [ - { - "instanceSpecifier": "abc/abc/TirePressurePort", - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "instances": [ - { - "instanceId": 1234, - "asil-level": "B", - "binding": "SHM", - "events": [], - "fields": [] - } - ] - } - ], - "global": { - "asil-level": "QM" - } - } -)json"; - INSTANTIATE_TEST_SUITE_P( InvalidProcessAsil, InvalidProcessAsil, ::testing::Values(R"json({"serviceTypes": [], "serviceInstances": [], "global": { "asil-level": "ANY" }})json", R"json({"serviceTypes": [], "serviceInstances": [], "global": { "asil-level": "Elefant" }})json", - R"json({"serviceTypes": [], "serviceInstances": [], "global": { "asil-level": "" }})json", - inconsistent_asil_config)); + R"json({"serviceTypes": [], "serviceInstances": [], "global": { "asil-level": "" }})json")); class InvalidMsgQueueSizeFixture : public ::testing::TestWithParam { @@ -2786,53 +2641,6 @@ TEST(ConfigurationJsonParsingStrategy, OnlyQmReceiverQueueSizes) GlobalConfiguration::DEFAULT_MIN_NUM_MESSAGES_TX_QUEUE); } -TEST(ConfigurationJsonParsingStrategy, WrongQualityTypeForAllowedUsersWillDie) -{ - // Given a JSON without necessary attribute `instance_id_` for SHM-Binding Info - auto j2 = R"( - { - "serviceTypes": [ - { - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "bindings": [] - } - ], - "serviceInstances": [ - { - "instanceSpecifier": "abc/abc/TirePressurePort", - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "instances": [ - { - "instanceId": 1234, - "asil-level": "B", - "binding": "SHM", - "shm-size": 10000, - "allowedConsumer": { - "QM": [ - 42, - 43 - ] - } - } - ] - } - ] - } -)"_json; - // When parsing the JSON - // That the application will terminate - SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( - score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2))); -} - TEST(ConfigurationJsonParsingStrategy, InvalidQualityTypeForAllowedConsumersWillDie) { // Given a JSON without invalid attribute consumer quality type @@ -3946,64 +3754,6 @@ TEST_F(ConfigurationJsonParsingStrategyFixture, NoDuplicateServiceInstanceFieldW score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2))); } -TEST_F(ConfigurationJsonParsingStrategyFixture, - SpecifyingServiceInstanceFieldWhichDoesNotCorrespondToAServiceTypeFieldWillDie) -{ - // Given a JSON with unknown field - auto j2 = R"( -{ - "serviceTypes": [ - { - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "bindings": [ - { - "binding": "SHM", - "serviceId": 1234, - "fields": [ - { - "fieldName": "CurrentTemperatureFrontLeft", - "fieldId": 30 - } - ] - } - ] - } - ], - "serviceInstances": [ - { - "instanceSpecifier": "abc/abc/TirePressurePort", - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "instances": [ - { - "instanceId": 1234, - "asil-level": "QM", - "binding": "SHM", - "fields": [ - { - "fieldName": "Unknown" - } - ] - } - ] - } - ] -} -)"_json; - - // When parsing the JSON - // That the application will terminate - SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( - score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2))); -} - TEST_F(ConfigurationJsonParsingStrategyFixture, SpecifyingServiceInstanceFieldWhichCorrespondToAServiceTypeFieldWillNotDie) { @@ -4439,56 +4189,6 @@ TEST_F(ConfigurationJsonParsingStrategyFixture, InvalidServiceInstanceSpecifierW score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2))); } -TEST_F(ConfigurationJsonParsingStrategyFixture, NoServiceTypeFieldsOrEventsWillDie) -{ - // Given a JSON with no service type fields or events - auto j2 = R"( -{ - "serviceTypes": [ - { - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "bindings": [] - } - ], - "serviceInstances": [ - { - "instanceSpecifier": "abc/abc/TirePressurePort", - "serviceTypeName": "/score/ncar/services/TirePressureService", - "version": { - "major": 12, - "minor": 34 - }, - "instances": [ - { - "instanceId": 1234, - "asil-level": "QM", - "binding": "SHM", - "shm-size": 10000, - "control-asil-b-shm-size": 20000, - "control-qm-shm-size": 30000, - "events": [ - { - "eventName": "CurrentPressureFrontLeft" - } - ] - } - ] - } - ] -} - - -)"_json; - // When parsing the JSON - // That the application will terminate - SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( - score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2))); -} - TEST_F(ConfigurationJsonParsingStrategyFixture, WithServiceTypeFieldsOrEventsWillNotDie) { // configuration is the same as the test above and is testing the positive case. diff --git a/score/mw/com/impl/configuration/configuration_test.cpp b/score/mw/com/impl/configuration/configuration_test.cpp index ea57bdd73..517c3c246 100644 --- a/score/mw/com/impl/configuration/configuration_test.cpp +++ b/score/mw/com/impl/configuration/configuration_test.cpp @@ -26,11 +26,13 @@ #include +#include #include #include #include #include #include +#include #include #include @@ -49,6 +51,13 @@ ConfigurationStore kConfigStoreQm{ LolaServiceInstanceId{1U}, }; +} // namespace + +// ConfigurationFixture is intentionally declared outside of the anonymous namespace above (and re-opened below): +// Configuration declares "friend class ConfigurationFixture;" which refers to +// score::mw::com::impl::ConfigurationFixture. Since an anonymous namespace introduces a distinct (uniquely-named) inner +// scope, a ConfigurationFixture nested inside it would be a different, unrelated class and would NOT receive the +// granted friendship. class ConfigurationFixture : public ::testing::Test { public: @@ -89,9 +98,24 @@ class ConfigurationFixture : public ::testing::Test } } + /// \brief Helper method to access the configuration's private member to compare merge results + static Configuration::ServiceTypeDeployments GetServiceTypesSnapshot(const Configuration& configuration) + { + return configuration.GetServiceTypes(); + } + + /// \brief Helper method to access the configuration's private member to compare merge results + static Configuration::ServiceInstanceDeployments GetServiceInstancesSnapshot(const Configuration& configuration) + { + return configuration.GetServiceInstances(); + } + std::optional unit_{}; }; +namespace +{ + score::Result GetStringFromJson(const json::Object& json_object) { json::JsonWriter json_writer{}; @@ -258,7 +282,6 @@ TEST_F(ConfigurationFixture, { // Given an empty configuration WithEmptyConfiguration(); - // When inserting a ServiceTypeDeployment with a unique ServiceIdentifierType const auto* const service_type_deployment_ptr = unit_.value().AddServiceTypeDeployment( kConfigStoreQm.service_identifier_, *kConfigStoreQm.service_type_deployment_); @@ -299,6 +322,11 @@ LolaEventInstanceDeployment MakeEventInstanceDeployment() return LolaEventInstanceDeployment{std::nullopt, std::nullopt, std::nullopt, false, 0U}; } +LolaFieldInstanceDeployment MakeFieldInstanceDeployment() +{ + return LolaFieldInstanceDeployment{MakeEventInstanceDeployment(), false, false}; +} + Configuration MakeConfigurationWithAsilLevel(const QualityType process_asil_level) { GlobalConfiguration global_configuration{}; @@ -439,6 +467,61 @@ TEST(ConfigurationValidateCrosscheckServiceInstancesToTypes, InstanceEventNotInS static_cast(configuration_errc::configuration_invalid_event_reference_from_instance)); } +TEST(ConfigurationValidateCrosscheckServiceInstancesToTypes, InstanceFieldNotInServiceTypeReturnsError) +{ + using namespace validate_test; + + // Given a Configuration where the service instance's field doesn't exist in the referenced service type + auto config = MakeConfigurationWithAsilLevel(QualityType::kASIL_QM); + const auto service_identifier = MakeServiceIdentifier(); + const auto instance_specifier = InstanceSpecifier::Create(std::string{kValidInstanceSpecifier}).value(); + + // Service type deployment does not contain "field_a". + config.AddServiceTypeDeployment(service_identifier, + ServiceTypeDeployment{LolaServiceTypeDeployment{LolaServiceId{1234U}, {}, {}, {}}}); + + LolaServiceInstanceDeployment lola_instance{LolaServiceInstanceId{1U}}; + lola_instance.fields_.emplace("field_a", MakeFieldInstanceDeployment()); + config.AddServiceInstanceDeployments( + instance_specifier, + ServiceInstanceDeployment{service_identifier, lola_instance, QualityType::kASIL_QM, instance_specifier}); + + // When validating the configuration + const auto result = config.Validate(); + + // Then validation fails with the expected error code + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(*result.error(), + static_cast(configuration_errc::configuration_invalid_field_reference_from_instance)); +} + +TEST(ConfigurationValidateCrosscheckServiceInstancesToTypes, + ServiceTypeWithoutLolaBindingReturnsUnsupportedTypeBindingError) +{ + using namespace validate_test; + + // Given a Configuration where the referenced service type has no (LoLa) binding at all, while the service + // instance which references it declares an event. + auto config = MakeConfigurationWithAsilLevel(QualityType::kASIL_QM); + const auto service_identifier = MakeServiceIdentifier(); + const auto instance_specifier = InstanceSpecifier::Create(std::string{kValidInstanceSpecifier}).value(); + + config.AddServiceTypeDeployment(service_identifier, ServiceTypeDeployment{score::cpp::blank{}}); + + LolaServiceInstanceDeployment lola_instance{LolaServiceInstanceId{1U}}; + lola_instance.events_.emplace("event_a", MakeEventInstanceDeployment()); + config.AddServiceInstanceDeployments( + instance_specifier, + ServiceInstanceDeployment{service_identifier, lola_instance, QualityType::kASIL_QM, instance_specifier}); + + // When validating the configuration + const auto result = config.Validate(); + + // Then validation fails because the referenced service type doesn't have a (LoLa) binding + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(*result.error(), static_cast(configuration_errc::configuration_unsupported_type_binding)); +} + TEST_F(ConfigurationFixture, HasLolaServiceDeploymentReturnsTrueIfLolaServiceTypeDeploymentExists) { // Given a configuration containing a LolaServiceTypeDeployment @@ -650,6 +733,282 @@ TEST_F(ConfigurationFixture, GetInstancesOfServiceTypeReturnsCorrectInstanceSpec EXPECT_TRUE(result.find("abc/abc/TirePressurePort1") != result.end()); EXPECT_TRUE(result.find("abc/abc/TirePressurePort3") != result.end()); } + +TEST_F(ConfigurationFixture, MergingTwoConfigurationsWithUniqueServiceIdentifierTypesAndInstanceSpecifiersSucceeds) +{ + // Given a configuration with at lest one entry... + WithMinimalConfiguration(); + + LolaServiceId service_id{1U}; + auto instance_specifier_string = InstanceSpecifier::Create(std::string{"/bla/blob/instance_specifier"}).value(); + ConfigurationStore config_store{ + instance_specifier_string, + make_ServiceIdentifierType("/bla/blob/one", 1U, 2U), + QualityType::kASIL_QM, + service_id, + LolaServiceInstanceId{1U}, + }; + + // ... and a second configuration that has an identical service instance entry + Configuration::ServiceTypeDeployments type_deployments{}; + type_deployments.insert({config_store.service_identifier_, *config_store.service_type_deployment_}); + Configuration::ServiceInstanceDeployments instance_deployments{}; + instance_deployments.emplace(config_store.instance_specifier_, *config_store.service_instance_deployment_); + + auto addon_configuration = + Configuration{type_deployments, instance_deployments, GlobalConfiguration{}, TracingConfiguration{}}; + + // When merging the two configurations + const auto merge_result = unit_.value().MergeServiceEntries(std::move(addon_configuration)); + + // Then the error code should be the expected one + EXPECT_TRUE(merge_result.has_value()); + EXPECT_EQ(unit_.value().GetNumberOfServiceTypes(), 2); + EXPECT_EQ(unit_.value().GetNumberOfServiceInstances(), 2); +} + +TEST_F(ConfigurationFixture, MergingIntoEmptyConfigurationLeadsToResultingConfigEqualsIncomingConfig) +{ + // Given an empty configuration ... + WithEmptyConfiguration(); + + // ... and an add-on configuration with some entries + Configuration::ServiceTypeDeployments type_deployments{}; + type_deployments.insert({kConfigStoreQm.service_identifier_, *kConfigStoreQm.service_type_deployment_}); + Configuration::ServiceInstanceDeployments instance_deployments{}; + instance_deployments.emplace(kConfigStoreQm.instance_specifier_, *kConfigStoreQm.service_instance_deployment_); + + auto addon_config = + Configuration{type_deployments, instance_deployments, GlobalConfiguration{}, TracingConfiguration{}}; + + // When merging both configurations + const auto merge_result = unit_.value().MergeServiceEntries(std::move(addon_config)); + + // Then merging should be successful and the resulting config should have the same entries as the add-on + // configuration + EXPECT_TRUE(merge_result.has_value()); + + EXPECT_EQ(GetServiceTypesSnapshot(unit_.value()), type_deployments); + EXPECT_EQ(GetServiceInstancesSnapshot(unit_.value()), instance_deployments); +} + +TEST_F(ConfigurationFixture, MergingEmptyConfigurationLeadsToResultingConfigEqualsInitialConfig) +{ + // Given a configuration with some entries + WithMinimalConfiguration(); + + const auto type_deployments_backup = GetServiceTypesSnapshot(unit_.value()); + const auto instance_deployments_backup = GetServiceInstancesSnapshot(unit_.value()); + + // ... and an empty configuration that shall be merged + auto addon_configuration = Configuration{Configuration::ServiceTypeDeployments{}, + Configuration::ServiceInstanceDeployments{}, + GlobalConfiguration{}, + TracingConfiguration{}}; + + // When merging these two configuration + const auto merge_result = unit_.value().MergeServiceEntries(std::move(addon_configuration)); + + // Then merging should be successful and the resulting config should have the same entries as the initial + // configuration + EXPECT_TRUE(merge_result.has_value()); + + EXPECT_EQ(GetServiceTypesSnapshot(unit_.value()), type_deployments_backup); + EXPECT_EQ(GetServiceInstancesSnapshot(unit_.value()), instance_deployments_backup); +} + +TEST_F(ConfigurationFixture, MergingWithDuplicateServiceTypeEntriesLeadsToError) +{ + // Given a configuration with at lest one entry... + WithMinimalConfiguration(); + + // ... and a second configuration that has an identical service type entry + Configuration::ServiceTypeDeployments type_deployments{}; + type_deployments.insert({kConfigStoreQm.service_identifier_, *kConfigStoreQm.service_type_deployment_}); + Configuration::ServiceInstanceDeployments instance_deployments{}; + instance_deployments.emplace(kConfigStoreQm.instance_specifier_, *kConfigStoreQm.service_instance_deployment_); + + auto addon_configuration = + Configuration{type_deployments, instance_deployments, GlobalConfiguration{}, TracingConfiguration{}}; + + // When merging the two configurations + const auto merge_result = unit_.value().MergeServiceEntries(std::move(addon_configuration)); + + // Then the error code should be the expected one + EXPECT_FALSE(merge_result.has_value()); + EXPECT_EQ(merge_result.error(), configuration_errc::configuration_merge_duplicate_service_type); +} + +TEST_F(ConfigurationFixture, MergingWithDuplicateServiceInstanceEntriesLeadsToError) +{ + // Given a configuration with at lest one entry... + WithMinimalConfiguration(); + + LolaServiceId service_id{1U}; + auto instance_specifier_string = InstanceSpecifier::Create(std::string{"/bla/blob/instance_specifier"}).value(); + ConfigurationStore config_store{ + instance_specifier_string, + make_ServiceIdentifierType("/bla/blob/one", 1U, 2U), + QualityType::kASIL_QM, + service_id, + LolaServiceInstanceId{1U}, + }; + + // ... and a second configuration that has an identical service instance entry + Configuration::ServiceTypeDeployments type_deployments{}; + type_deployments.insert({config_store.service_identifier_, *config_store.service_type_deployment_}); + Configuration::ServiceInstanceDeployments instance_deployments{}; + instance_deployments.emplace(kConfigStoreQm.instance_specifier_, *kConfigStoreQm.service_instance_deployment_); + + auto addon_configuration = + Configuration{type_deployments, instance_deployments, GlobalConfiguration{}, TracingConfiguration{}}; + + // When merging the two configurations + const auto merge_result = unit_.value().MergeServiceEntries(std::move(addon_configuration)); + + // Then the error code should be the expected one + EXPECT_FALSE(merge_result.has_value()); + EXPECT_EQ(merge_result.error(), configuration_errc::configuration_merge_duplicate_service_instance); +} + +TEST_F(ConfigurationFixture, MergingConfigurationsFromTwoThreadsConcurrentlySucceeds) +{ + // Given an empty configuration ... + WithEmptyConfiguration(); + + constexpr std::size_t kMergesPerThread{50U}; + + // ... and a helper which merges kMergesPerThread uniquely-named addon configurations (identified via + // thread_index/entry_index) into unit_ and records whether each merge succeeded. + auto merge_worker = [this](std::size_t thread_index) { + std::vector results{}; + results.reserve(kMergesPerThread); + for (std::size_t entry_index = 0U; entry_index < kMergesPerThread; ++entry_index) + { + const auto service_name = + "/thread" + std::to_string(thread_index) + "/service" + std::to_string(entry_index); + const auto instance_specifier_string = InstanceSpecifier::Create("/thread" + std::to_string(thread_index) + + "/instance" + std::to_string(entry_index)) + .value(); + + Configuration::ServiceTypeDeployments type_deployments{}; + type_deployments.insert( + {make_ServiceIdentifierType(service_name, 1U, 0U), *kConfigStoreQm.service_type_deployment_}); + Configuration::ServiceInstanceDeployments instance_deployments{}; + instance_deployments.emplace(instance_specifier_string, *kConfigStoreQm.service_instance_deployment_); + + Configuration addon_configuration{std::move(type_deployments), + std::move(instance_deployments), + GlobalConfiguration{}, + TracingConfiguration{}}; + + const auto merge_result = unit_.value().MergeServiceEntries(addon_configuration); + results.push_back(merge_result.has_value()); + } + return results; + }; + + // When merging configurations into the shared unit_ concurrently from two different threads, each with its own + // uniquely-named set of service types/instances (so no clashes can occur between the threads) + std::vector results_thread_0{}; + std::vector results_thread_1{}; + std::thread thread_0([&results_thread_0, &merge_worker]() { + results_thread_0 = merge_worker(0U); + }); + std::thread thread_1([&results_thread_1, &merge_worker]() { + results_thread_1 = merge_worker(1U); + }); + thread_0.join(); + thread_1.join(); + + // Then every single merge should have succeeded ... + EXPECT_EQ(results_thread_0.size(), kMergesPerThread); + EXPECT_EQ(results_thread_1.size(), kMergesPerThread); + EXPECT_TRUE(std::all_of(results_thread_0.begin(), results_thread_0.end(), [](bool ok) { + return ok; + })); + EXPECT_TRUE(std::all_of(results_thread_1.begin(), results_thread_1.end(), [](bool ok) { + return ok; + })); + + // ... and the resulting configuration should contain all entries from both threads, without any lost updates. + EXPECT_EQ(unit_.value().GetNumberOfServiceTypes(), 2U * kMergesPerThread); + EXPECT_EQ(unit_.value().GetNumberOfServiceInstances(), 2U * kMergesPerThread); +} + +TEST_F(ConfigurationFixture, MergingConfigurationDoesNotBreakExistingReferences) +{ + // Given a minimal configuration with at least one entry, ... + WithMinimalConfiguration(); + // ...for which we can store a reference to + const auto service_type_deployment = + unit_.value().GetServiceTypeDeployment(make_ServiceIdentifierType("/bla/blub/one", 1U, 2U)).value().get(); + const auto service_instance_deployment = + unit_.value() + .GetServiceInstanceDeployment( + InstanceSpecifier::Create(std::string{"/bla/blub/instance_specifier"}).value()) + .value() + .get(); + + // ... and a second configuration with another valid entry + LolaServiceId service_id{1U}; + auto instance_specifier_string = InstanceSpecifier::Create(std::string{"/bla/blob/instance_specifier"}).value(); + ConfigurationStore config_store{ + instance_specifier_string, + make_ServiceIdentifierType("/bla/blob/one", 1U, 2U), + QualityType::kASIL_QM, + service_id, + LolaServiceInstanceId{1U}, + }; + + Configuration::ServiceTypeDeployments type_deployments{}; + type_deployments.insert({config_store.service_identifier_, *config_store.service_type_deployment_}); + Configuration::ServiceInstanceDeployments instance_deployments{}; + instance_deployments.emplace(config_store.instance_specifier_, *config_store.service_instance_deployment_); + + auto addon_configuration = + Configuration{type_deployments, instance_deployments, GlobalConfiguration{}, TracingConfiguration{}}; + + // When merging the two configurations + const auto merge_result = unit_.value().MergeServiceEntries(std::move(addon_configuration)); + + const auto lola_service_type_deployment = + std::get(service_type_deployment.binding_info_); + + // Then the merge should be successful, and we should still be able to access the previously given reference + EXPECT_TRUE(merge_result.has_value()); + EXPECT_EQ(lola_service_type_deployment.service_id_, 1); + EXPECT_EQ(service_instance_deployment.instance_specifier_.ToString(), "/bla/blub/instance_specifier"); +} + +TEST_F(ConfigurationFixture, MergingIntoConfigurationWithInvalidStateReturnsError) +{ + // Given a configuration with at least one entry, that has then been moved-from (leaving its internal service + // type / instance deployment pointers reset to null)... + WithMinimalConfiguration(); + const Configuration configuration_moved_to{std::move(unit_.value())}; + + // Sanity check: the object moved into should be unaffected and still contain the original entries + EXPECT_EQ(configuration_moved_to.GetNumberOfServiceTypes(), 1); + EXPECT_EQ(configuration_moved_to.GetNumberOfServiceInstances(), 1); + + // ... and a valid add-on configuration to merge in + Configuration::ServiceTypeDeployments type_deployments{}; + type_deployments.insert({kConfigStoreQm.service_identifier_, *kConfigStoreQm.service_type_deployment_}); + Configuration::ServiceInstanceDeployments instance_deployments{}; + instance_deployments.emplace(kConfigStoreQm.instance_specifier_, *kConfigStoreQm.service_instance_deployment_); + + auto addon_configuration = + Configuration{type_deployments, instance_deployments, GlobalConfiguration{}, TracingConfiguration{}}; + + // When attempting to merge the add-on configuration into the now invalid configuration + const auto merge_result = unit_.value().MergeServiceEntries(std::move(addon_configuration)); + + // Then the merge should fail with the expected error + EXPECT_FALSE(merge_result.has_value()); + EXPECT_EQ(merge_result.error(), configuration_errc::configuration_merged_invalid_configuration_state); +} + using ConfigurationDeathTest = ConfigurationFixture; TEST_F(ConfigurationDeathTest, AddingAServiceTypeDeploymentWithDuplicateServiceIdentifierTypeTerminates) { diff --git a/score/mw/com/impl/configuration/example/mw_com_config_to_merge.json b/score/mw/com/impl/configuration/example/mw_com_config_to_merge.json new file mode 100644 index 000000000..bcbf85800 --- /dev/null +++ b/score/mw/com/impl/configuration/example/mw_com_config_to_merge.json @@ -0,0 +1,99 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/score/ncar/services/TirePressureExtendedService", + "version": { + "major": 12, + "minor": 34 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 1234, + "events": [ + { + "eventName": "CurrentPressureFrontLeft", + "eventId": 20 + } + ], + "fields": [ + { + "fieldName": "CurrentTemperatureFrontLeft", + "fieldId": 30 + } + ], + "methods": [ + { + "methodName": "SetPressure", + "methodId": 40 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "abc/abc/TirePressurePort", + "serviceTypeName": "/score/ncar/services/TirePressureExtendedService", + "version": { + "major": 12, + "minor": 34 + }, + "instances": [ + { + "instanceId": 1234, + "asil-level": "B", + "binding": "SHM", + "shm-size": 10000, + "control-asil-b-shm-size": 20000, + "control-qm-shm-size": 30000, + "events": [ + { + "eventName": "CurrentPressureFrontLeft", + "numberOfSampleSlots": 50, + "maxSubscribers": 5, + "numberOfIpcTracingSlots": 0 + } + ], + "fields": [ + { + "fieldName": "CurrentTemperatureFrontLeft", + "numberOfSampleSlots": 60, + "maxSubscribers": 6, + "numberOfIpcTracingSlots": 7, + "useGetIfAvailable": true, + "useSetIfAvailable": true + } + ], + "methods": [ + { + "methodName": "SetPressure", + "queueSize": 20 + } + ], + "allowedConsumer": { + "QM": [ + 42, + 43 + ], + "B": [ + 54, + 55 + ] + }, + "allowedProvider": { + "QM": [ + 15 + ], + "B": [ + 15 + ] + }, + "interVmSupport": true, + "interVmForwarded": true + } + ] + } + ] +} diff --git a/score/mw/com/impl/configuration/example/mw_com_config_to_merge_second.json b/score/mw/com/impl/configuration/example/mw_com_config_to_merge_second.json new file mode 100644 index 000000000..5fdc76f5e --- /dev/null +++ b/score/mw/com/impl/configuration/example/mw_com_config_to_merge_second.json @@ -0,0 +1,87 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/score/ncar/services/BrakePressureService", + "version": { + "major": 12, + "minor": 34 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 1234, + "events": [ + { + "eventName": "CurrentPressureFrontLeft", + "eventId": 20 + } + ], + "fields": [ + { + "fieldName": "CurrentTemperatureFrontLeft", + "fieldId": 30 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "abc/abc/BrakePressurePort", + "serviceTypeName": "/score/ncar/services/BrakePressureService", + "version": { + "major": 12, + "minor": 34 + }, + "instances": [ + { + "instanceId": 1234, + "asil-level": "B", + "binding": "SHM", + "shm-size": 10000, + "control-asil-b-shm-size": 20000, + "control-qm-shm-size": 30000, + "events": [ + { + "eventName": "CurrentPressureFrontLeft", + "numberOfSampleSlots": 50, + "maxSubscribers": 5, + "numberOfIpcTracingSlots": 0 + } + ], + "fields": [ + { + "fieldName": "CurrentTemperatureFrontLeft", + "numberOfSampleSlots": 60, + "maxSubscribers": 6, + "numberOfIpcTracingSlots": 7, + "useGetIfAvailable": true, + "useSetIfAvailable": true + } + ], + "allowedConsumer": { + "QM": [ + 42, + 43 + ], + "B": [ + 54, + 55 + ] + }, + "allowedProvider": { + "QM": [ + 15 + ], + "B": [ + 15 + ] + }, + "interVmSupport": true, + "interVmForwarded": true + } + ] + } + ] +} diff --git a/score/mw/com/impl/runtime.cpp b/score/mw/com/impl/runtime.cpp index ec26fb4e0..dbc831c80 100644 --- a/score/mw/com/impl/runtime.cpp +++ b/score/mw/com/impl/runtime.cpp @@ -123,9 +123,48 @@ void Runtime::Initialize(const runtime::RuntimeConfiguration& runtime_configurat } auto config = configuration::Parse(runtime_configuration.GetConfigurationPath().Native()); + const auto validation_result = config.Validate(); + + if (!validation_result.has_value()) + { + ::score::mw::log::LogFatal("lola") << validation_result.error().UserMessage(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(false); + } score::cpp::ignore = initialization_config_.emplace(std::move(config)); } +Result Runtime::InitializeRuntimeAddonConfiguration(const runtime::RuntimeConfiguration& runtime_configuration) +{ + auto config = configuration::Parse(runtime_configuration.GetConfigurationPath().Native()); + + return HandleAddonConfiguration(config); +} + +Result Runtime::InitializeRuntimeAddonConfiguration(score::json::Any json) +{ + auto config = configuration::Parse(std::move(json)); + + return HandleAddonConfiguration(config); +} + +Result Runtime::HandleAddonConfiguration(const Configuration& config) noexcept +{ + // TODO This check is not complete and should be extended + if (config.GetGlobalConfiguration().GetApplicationId().has_value()) + { + mw::log::LogWarn("lola") << "Add-on configuration contains global configuration data that will be ignored. " + "Please remove global configuration from add-on configuration."; + } + + const auto merge_result = Runtime::getInstanceInternal().MergeAdditionalConfiguration(std::move(config)); + if (!merge_result.has_value()) + { + mw::log::LogError("lola") << merge_result.error(); + std::terminate(); + } + return {}; +} + auto Runtime::getInstance() -> IRuntime& { if (mock_ != nullptr) @@ -245,6 +284,25 @@ std::vector Runtime::resolve(const InstanceSpecifier& specif return result; } +Result Runtime::MergeAdditionalConfiguration(const Configuration& additional_configuration) noexcept +{ + const auto merge_result = configuration_.MergeServiceEntries(std::move(additional_configuration)); + if (!merge_result.has_value()) + { + return merge_result; + } + + const auto validation_result = configuration_.Validate(); + + if (!validation_result.has_value()) + { + ::score::mw::log::LogFatal("lola") << validation_result.error().UserMessage(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(false); + } + + return {}; +} + auto Runtime::GetBindingRuntime(const BindingType binding) const noexcept -> IBindingRuntime* { auto search = binding_runtimes_.find(binding); diff --git a/score/mw/com/impl/runtime.h b/score/mw/com/impl/runtime.h index b8ae91d05..ed3b267ba 100644 --- a/score/mw/com/impl/runtime.h +++ b/score/mw/com/impl/runtime.h @@ -76,6 +76,22 @@ class Runtime final : public IRuntime /// \param runtime_configuration object containing configuration needed to initialize the Runtime static void Initialize(const runtime::RuntimeConfiguration& runtime_configuration); + /// \brief Extends mw::com subsystem with the given add-on configuration. + /// \details This call is optional and shall allow loading additional mw::com configuration files in order to extend + /// already loaded configurations. + /// \attention This function will call std::terminate() in case no initial configuration has been loaded yet, or + /// that the configuration is incompatible to the previously loaded one. + /// \param runtime_configuration object containing service definitions which should be added to existing set + static Result InitializeRuntimeAddonConfiguration(const runtime::RuntimeConfiguration& runtime_configuration); + + /// \brief Extends mw::com subsystem with the given add-on configuration provided as a JSON object. + /// \details This call is optional and shall allow loading additional mw::com configuration as an in-memory JSON + /// object in order to extend already loaded configurations. + /// \attention This function will call std::terminate() in case no initial configuration has been loaded yet, or + /// that the configuration is incompatible to the previously loaded one. + /// \param json object containing service definitions which should be added to existing set + static Result InitializeRuntimeAddonConfiguration(score::json::Any json); + /// \brief get singleton. /// \details Might return either reference to a real Runtime instance or to a mock. /// \return singleton ref. @@ -131,6 +147,14 @@ class Runtime final : public IRuntime /// \pre the internal static initialization_config_ has to be initialized with a Configuration. static Runtime& getInstanceInternal(); + /// \brief Extend loaded configuration with the Configuration provided as a parameter. Returns an error if + /// configurations are incompatible or there is no regular (complete) configuration loaded yet. + static Result HandleAddonConfiguration(const Configuration& config) noexcept; + + /// \brief Merges the service types and instances into the already loaded configuration. Returns an error if one of + /// those entries in the given configuration already exists in this configuration. + Result MergeAdditionalConfiguration(const Configuration& additional_configuration) noexcept; + /// \brief pointer to a mock to be used (set via InjectMock()) static score::mw::com::impl::IRuntime* mock_; diff --git a/score/mw/com/impl/runtime_single_exec_test.cpp b/score/mw/com/impl/runtime_single_exec_test.cpp index 96ad71a61..6dfca3899 100644 --- a/score/mw/com/impl/runtime_single_exec_test.cpp +++ b/score/mw/com/impl/runtime_single_exec_test.cpp @@ -94,7 +94,8 @@ class RuntimeSingleTestPerProcessFixture : public singleton::test::SingleTestPer InstanceSpecifier tire_pressure_port_other_{ InstanceSpecifier::Create(std::string{"abc/abc/TirePressurePortOther"}).value()}; std::string config_with_tire_pressure_port_other_{get_path("mw_com_config_other.json")}; - bool tested_in_separate_process_{false}; + std::string config_to_merge_{get_path("mw_com_config_to_merge.json")}; + std::string config_to_merge_second_{get_path("mw_com_config_to_merge_second.json")}; }; std::vector GetEventNameListFromHandle(const HandleType& handle_type) noexcept @@ -263,6 +264,101 @@ TEST_F(RuntimeInitializationTest, ImplicitInitializationLoadsCorrectConfiguratio }); } +TEST_F(RuntimeInitializationTest, ConfigurationGetsMergedAndLoadedIfInitialConfigurationHasBeenLoadedEarlier) +{ + TestInSeparateProcess([this]() { + // Given an initial complete configuration has been loaded earlier + const auto configuration = runtime::RuntimeConfiguration{config_with_tire_pressure_port_other_}; + Runtime::Initialize(configuration); + + // When loading an add-on configuration with InitializeRuntimeAddonConfiguration + const auto addon_init_result = + Runtime::InitializeRuntimeAddonConfiguration(runtime::RuntimeConfiguration{config_to_merge_}); + + auto& updated_runtime = static_cast(Runtime::getInstance()); + + const RuntimeAttorney attorney{updated_runtime}; + + // Then both configurations are loaded and merged into the runtime's configuration + EXPECT_TRUE(addon_init_result.has_value()); + EXPECT_EQ(attorney.GetConfigurationAddress()->GetNumberOfServiceTypes(), 2); + }); +} + +TEST_F(RuntimeInitializationTest, ConcurrentAddonConfigurationInitializationSucceeds) +{ + TestInSeparateProcess([this]() { + // Given an initial configuration has been loaded + const auto configuration = runtime::RuntimeConfiguration{config_with_tire_pressure_port_other_}; + Runtime::Initialize(configuration); + + // When two threads concurrently add non-conflicting addon configurations + std::optional> result_thread_1{}; + std::optional> result_thread_2{}; + + std::thread thread_1{[&result_thread_1, this]() { + result_thread_1 = + Runtime::InitializeRuntimeAddonConfiguration(runtime::RuntimeConfiguration{config_to_merge_}); + }}; + + std::thread thread_2{[&result_thread_2, this]() { + result_thread_2 = + Runtime::InitializeRuntimeAddonConfiguration(runtime::RuntimeConfiguration{config_to_merge_second_}); + }}; + + thread_1.join(); + thread_2.join(); + + // Then both addon configurations are successfully merged + ASSERT_TRUE(result_thread_1.has_value()); + ASSERT_TRUE(result_thread_2.has_value()); + EXPECT_TRUE(result_thread_1->has_value()); + EXPECT_TRUE(result_thread_2->has_value()); + + auto& runtime = static_cast(Runtime::getInstance()); + const RuntimeAttorney attorney{runtime}; + + // And all three configurations (initial + 2 addons) are present + EXPECT_EQ(attorney.GetConfigurationAddress()->GetNumberOfServiceTypes(), 3); + }); +} + +using RuntimeInitializationDeathTest = RuntimeInitializationTest; +TEST_F(RuntimeInitializationDeathTest, InitializationFailsIfNoAppConfigurationHasBeenLoadedYet) +{ + // EXPECT_DEATH forks a child process and GTest only allows one stderr capturer at a time + tested_in_separate_process_ = true; + + EXPECT_DEATH( + { + // Given no configuration has been loaded + const auto runtime_configuration = runtime::RuntimeConfiguration{config_with_tire_pressure_port_}; + // When loading an add-on configuration via InitializeRuntimeAddonConfiguration() + std::ignore = Runtime::InitializeRuntimeAddonConfiguration(runtime_configuration); + // Then the process terminates via std::terminate() + }, + ".*"); +} + +TEST_F(RuntimeInitializationDeathTest, AddOnConfigurationInitializationFailsIfMergingTheConfigurationFails) +{ + // EXPECT_DEATH forks a child process and GTest only allows one stderr capturer at a time + tested_in_separate_process_ = true; + + EXPECT_DEATH( + { + // Given an add-on configuration has been loaded, and it is being locked by accessing the Runtime instance + const auto runtime_configuration = runtime::RuntimeConfiguration{config_with_tire_pressure_port_}; + Runtime::Initialize(runtime_configuration); + std::ignore = static_cast(Runtime::getInstance()); + // When loading the same configuration via InitializeRuntimeAddonConfiguration() + const auto add_on_configuration = runtime::RuntimeConfiguration{config_with_tire_pressure_port_}; + std::ignore = Runtime::InitializeRuntimeAddonConfiguration(add_on_configuration); + // Then the process terminates via std::terminate() because there is a clash of service identifiers + }, + ".*"); +} + using RuntimeTest = RuntimeSingleTestPerProcessFixture; TEST_F(RuntimeTest, CannotResolveUnknownInstanceSpecifier) diff --git a/score/mw/com/runtime.cpp b/score/mw/com/runtime.cpp index dd18c3e87..c528032d9 100644 --- a/score/mw/com/runtime.cpp +++ b/score/mw/com/runtime.cpp @@ -86,4 +86,14 @@ void InitializeRuntime(const RuntimeConfiguration& runtime_configuration) impl::Runtime::Initialize(runtime_configuration); } +Result InitializeRuntimeAddonConfiguration(const RuntimeConfiguration& runtime_configuration) +{ + return impl::Runtime::InitializeRuntimeAddonConfiguration(runtime_configuration); +} + +Result InitializeRuntimeAddonConfiguration(score::json::Any json) +{ + return impl::Runtime::InitializeRuntimeAddonConfiguration(std::move(json)); +} + } // namespace score::mw::com::runtime diff --git a/score/mw/com/runtime.h b/score/mw/com/runtime.h index f33cd882c..69eae7197 100644 --- a/score/mw/com/runtime.h +++ b/score/mw/com/runtime.h @@ -118,6 +118,27 @@ void InitializeRuntime(const cpp::span command_line_argum */ void InitializeRuntime(const RuntimeConfiguration& runtime_configuration); +/** + * \api + * \brief Extends mw::com subsystem with the given add-on configuration. + * \details This call is optional and shall allow loading additional mw::com configuration files in order to extend + * already loaded configurations. + * \attention This function will call std::terminate() in case that the configuration is incompatible to the previously + * loaded one or if no complete mw::com configuration has been loaded previously. + **/ +Result InitializeRuntimeAddonConfiguration(const RuntimeConfiguration& runtime_configuration); + +/** + * \api + * \brief Extends mw::com subsystem with the given add-on configuration provided as a JSON blob. + * \details This call is optional and shall allow loading additional mw::com configuration as an in-memory JSON + * object in order to extend already loaded configurations. + * \attention This function will call std::terminate() in case that the configuration is incompatible to the previously + * loaded one or if no complete mw::com configuration has been loaded previously. + * \param json The JSON object containing the add-on configuration. + **/ +Result InitializeRuntimeAddonConfiguration(score::json::Any json); + } // namespace score::mw::com::runtime #endif // SCORE_MW_COM_RUNTIME_H