diff --git a/score/mw/com/api_surface.lock.json b/score/mw/com/api_surface.lock.json index c4d6feecc..33102b20c 100644 --- a/score/mw/com/api_surface.lock.json +++ b/score/mw/com/api_surface.lock.json @@ -1,5 +1,5 @@ { - "target": "//score/mw/com:com", + "target": ":com", "symbols": [ { "name": "ComErrc", @@ -31,6 +31,18 @@ "kind": "function", "signature": "InitializeRuntime : void (const std::int32_t, score::StringLiteral *)" }, + { + "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": "InitializeRuntime", "qualified_name": "score::mw::com::runtime::InitializeRuntime", @@ -385,7 +397,7 @@ "name": "ErrorDomain", "qualified_name": "score::mw::com::ComErrorDomain::ErrorDomain", "kind": "constructor", - "signature": "ErrorDomain : void ()" + "signature": "ErrorDomain : void () noexcept" }, { "name": "MessageFor", @@ -393,6 +405,132 @@ "kind": "method", "signature": "MessageFor : std::string_view (const score::result::ErrorCode &) const noexcept" }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::lola::ElementFqId &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::lola::ProxyMethodInstanceIdentifier &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::lola::UniqueMethodIdentifier &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::lola::ProxyInstanceIdentifier &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::lola::LolaServiceInstanceIdentifier &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::lola::SkeletonInstanceIdentifier &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : size_t (const score::mw::com::impl::ServiceIdentifierType &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::FindServiceHandle &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::HandleType &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::InstanceIdentifier &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : std::size_t (const score::mw::com::impl::InstanceSpecifier &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : size_t (const score::mw::com::impl::tracing::ServiceElementIdentifier &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : size_t (const score::mw::com::impl::tracing::ServiceElementIdentifierView &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : size_t (const score::mw::com::impl::tracing::ServiceElementInstanceIdentifierView &) const noexcept" + }, + { + "name": "operator()", + "qualified_name": "std::operator()", + "kind": "method", + "signature": "operator() : size_t (const score::mw::com::impl::tracing::TracePointKey &) const noexcept" + }, + { + "name": "IRuntime", + "qualified_name": "score::mw::com::runtime::IRuntime", + "kind": "class", + "signature": "class IRuntime" + }, + { + "name": "IRuntime", + "qualified_name": "score::mw::com::runtime::IRuntime::IRuntime", + "kind": "constructor", + "signature": "IRuntime : void ()" + }, + { + "name": "~IRuntime", + "qualified_name": "score::mw::com::runtime::IRuntime::~IRuntime", + "kind": "destructor", + "signature": "~IRuntime : void ()" + }, + { + "name": "ResolveInstanceIDs", + "qualified_name": "score::mw::com::runtime::IRuntime::ResolveInstanceIDs", + "kind": "method", + "signature": "ResolveInstanceIDs : score::Result (const InstanceSpecifier)" + }, + { + "name": "InitializeRuntime", + "qualified_name": "score::mw::com::runtime::IRuntime::InitializeRuntime", + "kind": "method", + "signature": "InitializeRuntime : void (const std::int32_t, score::cpp::span)" + }, + { + "name": "InitializeRuntime", + "qualified_name": "score::mw::com::runtime::IRuntime::InitializeRuntime", + "kind": "method", + "signature": "InitializeRuntime : void (const runtime::RuntimeConfiguration &)" + }, { "name": "RuntimeConfiguration", "qualified_name": "score::mw::com::runtime::RuntimeConfiguration::RuntimeConfiguration", @@ -477,23 +615,35 @@ "kind": "template_type_alias", "signature": "using SamplePtr = impl::SamplePtr" }, + { + "name": "Get", + "qualified_name": "score::mw::com::SamplePtr::Get", + "kind": "method", + "signature": "Get : pointer () const noexcept" + }, + { + "name": "Reset", + "qualified_name": "score::mw::com::SamplePtr::Reset", + "kind": "method", + "signature": "Reset : void (SamplePtr) noexcept" + }, { "name": "SamplePtr", "qualified_name": "score::mw::com::SamplePtr::SamplePtr", - "kind": "constructor", - "signature": "SamplePtr : void () noexcept" + "kind": "template_function", + "signature": "SamplePtr" }, { "name": "SamplePtr", "qualified_name": "score::mw::com::SamplePtr::SamplePtr", "kind": "constructor", - "signature": "SamplePtr : void (std::nullptr_t) noexcept" + "signature": "SamplePtr : void () noexcept" }, { "name": "SamplePtr", "qualified_name": "score::mw::com::SamplePtr::SamplePtr", "kind": "constructor", - "signature": "SamplePtr : void (pointer, ConsumerEventDataControlLocalView<> &, const SlotIndexType) noexcept" + "signature": "SamplePtr : void (std::nullptr_t) noexcept" }, { "name": "SamplePtr", @@ -501,6 +651,12 @@ "kind": "constructor", "signature": "SamplePtr : void (SamplePtr &&) noexcept" }, + { + "name": "Swap", + "qualified_name": "score::mw::com::SamplePtr::Swap", + "kind": "method", + "signature": "Swap : void (SamplePtr &) noexcept" + }, { "name": "get", "qualified_name": "score::mw::com::SamplePtr::get", @@ -511,7 +667,7 @@ "name": "operator*", "qualified_name": "score::mw::com::SamplePtr::operator*", "kind": "template_function", - "signature": "operator* : typename std::add_lvalue_reference::type () const noexcept" + "signature": "operator* : std::add_lvalue_reference_t () const noexcept" }, { "name": "operator->", @@ -519,17 +675,29 @@ "kind": "method", "signature": "operator-> : pointer () const noexcept" }, + { + "name": "operator<", + "qualified_name": "score::mw::com::SamplePtr::operator<", + "kind": "method", + "signature": "operator< : bool (const SamplePtr &) const noexcept" + }, { "name": "operator=", "qualified_name": "score::mw::com::SamplePtr::operator=", "kind": "method", - "signature": "operator= : SamplePtr &(std::nullptr_t) & noexcept" + "signature": "operator= : SamplePtr &(SamplePtr &&) & noexcept" }, { "name": "operator=", "qualified_name": "score::mw::com::SamplePtr::operator=", "kind": "method", - "signature": "operator= : SamplePtr &(SamplePtr &&) noexcept" + "signature": "operator= : SamplePtr &(std::nullptr_t) noexcept" + }, + { + "name": "operator>", + "qualified_name": "score::mw::com::SamplePtr::operator>", + "kind": "method", + "signature": "operator> : bool (const SamplePtr &) const noexcept" }, { "name": "~SamplePtr", @@ -544,10 +712,10 @@ "signature": "using SampleAllocateePtr = impl::SampleAllocateePtr" }, { - "name": "GetReferencedSlot", - "qualified_name": "score::mw::com::SampleAllocateePtr::GetReferencedSlot", + "name": "Get", + "qualified_name": "score::mw::com::SampleAllocateePtr::Get", "kind": "method", - "signature": "GetReferencedSlot : SlotIndexType () const noexcept" + "signature": "Get : pointer () const noexcept" }, { "name": "SampleAllocateePtr", @@ -561,12 +729,6 @@ "kind": "constructor", "signature": "SampleAllocateePtr : void (std::nullptr_t) noexcept" }, - { - "name": "SampleAllocateePtr", - "qualified_name": "score::mw::com::SampleAllocateePtr::SampleAllocateePtr", - "kind": "constructor", - "signature": "SampleAllocateePtr : void (pointer, const EventDataControlComposite<> &, ConsumerEventDataControlLocalView<> &, const SlotIndexType) noexcept" - }, { "name": "SampleAllocateePtr", "qualified_name": "score::mw::com::SampleAllocateePtr::SampleAllocateePtr", @@ -574,34 +736,34 @@ "signature": "SampleAllocateePtr : void (SampleAllocateePtr &&) noexcept" }, { - "name": "get", - "qualified_name": "score::mw::com::SampleAllocateePtr::get", + "name": "Swap", + "qualified_name": "score::mw::com::SampleAllocateePtr::Swap", "kind": "method", - "signature": "get : pointer () const noexcept" + "signature": "Swap : void (SampleAllocateePtr &) noexcept" }, { "name": "operator*", "qualified_name": "score::mw::com::SampleAllocateePtr::operator*", - "kind": "method", + "kind": "template_function", "signature": "operator* : typename std::add_lvalue_reference::type () const noexcept" }, { "name": "operator->", "qualified_name": "score::mw::com::SampleAllocateePtr::operator->", - "kind": "method", + "kind": "template_function", "signature": "operator-> : pointer () const noexcept" }, { "name": "operator=", "qualified_name": "score::mw::com::SampleAllocateePtr::operator=", "kind": "method", - "signature": "operator= : SampleAllocateePtr &(std::nullptr_t) & noexcept" + "signature": "operator= : SampleAllocateePtr &(SampleAllocateePtr &&) & noexcept" }, { "name": "operator=", "qualified_name": "score::mw::com::SampleAllocateePtr::operator=", "kind": "method", - "signature": "operator= : SampleAllocateePtr &(SampleAllocateePtr &&) & noexcept" + "signature": "operator= : SampleAllocateePtr &(std::nullptr_t) noexcept" }, { "name": "reset", @@ -609,12 +771,6 @@ "kind": "method", "signature": "reset : void () noexcept" }, - { - "name": "swap", - "qualified_name": "score::mw::com::SampleAllocateePtr::swap", - "kind": "method", - "signature": "swap : void (SampleAllocateePtr &) noexcept" - }, { "name": "~SampleAllocateePtr", "qualified_name": "score::mw::com::SampleAllocateePtr::~SampleAllocateePtr", @@ -685,7 +841,7 @@ "name": "GenericProxyEvent", "qualified_name": "score::mw::com::GenericProxyEvent::GenericProxyEvent", "kind": "constructor", - "signature": "GenericProxyEvent : void (ProxyBase &, const std::string_view, std::unique_ptr)" + "signature": "GenericProxyEvent : void (const std::string_view, std::unique_ptr)" }, { "name": "GenericProxyEvent", @@ -721,7 +877,7 @@ "name": "ProxyEventBase", "qualified_name": "score::mw::com::GenericProxyEvent::ProxyEventBase", "kind": "constructor", - "signature": "ProxyEventBase : void (std::string_view, std::unique_ptr) noexcept" + "signature": "ProxyEventBase : void (std::string_view, Result>) noexcept" }, { "name": "ProxyEventBase", diff --git a/score/mw/com/design/configuration/structural_view.puml b/score/mw/com/design/configuration/structural_view.puml index ae481e6d5..e0c2aea37 100644 --- a/score/mw/com/design/configuration/structural_view.puml +++ b/score/mw/com/design/configuration/structural_view.puml @@ -88,6 +88,7 @@ package "configuration" { + GetServiceInstances() const noexcept : const ServiceInstanceDeployments& + GetGlobalConfiguration() const noexcept : const GlobalConfiguration& + GetTracingConfiguration() const noexcept : const TracingConfiguration& + + MergeServiceEntries(const Configuration& additional_configuration) noexcept : Result } class "score::mw::com::impl::LolaServiceTypeDeployment" { @@ -199,16 +200,20 @@ class "nlhomann::json" { class "score::mw::com::impl::Runtime" { - configuration : Configuration const& + - configuration_mutex_ : std::mutex {mutable} __ - Runtime(std::pair&&> configs) {static} + Initialize() : void {static} + Initialize(const score::cpp::span arguments) : void + {static} + InitializeRuntimeAddonConfiguration(const runtime::RuntimeConfiguration& runtime_configuration) : Result + {static} + InitializeRuntimeAddonConfiguration(score::json::Any json) : Result {static} + getInstance() : Runtime& + resolve(const InstanceSpecifier&): score::mw::com::InstanceIdentifierContainer + GetBindingRuntime(const BindingType binding) const : IBindingRuntime* + GetServiceDiscovery() : IServiceDiscovery& + GetTracingFilterConfig() const : const tracing::ITracingFilterConfig* + GetTracingRuntime() const : tracing::ITracingRuntime* + - MergeAdditionalConfiguration(const Configuration& additional_configuration) : Result .. Notes: AUTOSAR SWS talks about a Runtime singleton in chapter 7.1.2 Design decisions without directly requiring it! diff --git a/score/mw/com/design/configuration/structural_view_refactored.puml b/score/mw/com/design/configuration/structural_view_refactored.puml index aec7b7674..09218894b 100644 --- a/score/mw/com/design/configuration/structural_view_refactored.puml +++ b/score/mw/com/design/configuration/structural_view_refactored.puml @@ -92,6 +92,7 @@ package "Configuration (unchanged core)" { + GetTracingConfiguration() const noexcept : const TracingConfiguration& + AddServiceTypeDeployment(...) noexcept : ServiceTypeDeployment* + AddServiceInstanceDeployments(...) noexcept : ServiceInstanceDeployment* + + MergeServiceEntries(const Configuration& additional_configuration) noexcept : Result } class "ServiceTypeDeployment" { @@ -128,11 +129,15 @@ package "Configuration (unchanged core)" { class "score::mw::com::impl::Runtime" { - configuration_ : Configuration const& - parsing_strategy_ : std::unique_ptr + - configuration_mutex_ : std::mutex {mutable} __ {static} + Initialize() : void {static} + Initialize(arguments : score::cpp::span) : void + {static} + InitializeRuntimeAddonConfiguration(const runtime::RuntimeConfiguration& runtime_configuration) : Result + {static} + InitializeRuntimeAddonConfiguration(score::json::Any json) : Result {static} + getInstance() : Runtime& + resolve(const InstanceSpecifier&) : InstanceIdentifierContainer + - MergeAdditionalConfiguration(const Configuration& additional_configuration) : Result } ' Relationships diff --git a/score/mw/com/design/runtime/runtime_structural_view.puml b/score/mw/com/design/runtime/runtime_structural_view.puml index 8c43fc7c5..c6f8de1bd 100644 --- a/score/mw/com/design/runtime/runtime_structural_view.puml +++ b/score/mw/com/design/runtime/runtime_structural_view.puml @@ -13,13 +13,17 @@ enum BindingType { class "mw::com::impl::Runtime" as Runtime { -binding_runtimes_ : std::unordered_map> + -configuration_mutex_ : std::mutex {mutable} +{static} Initialize() : void +{static} Initialize(int argc, score::StringLiteral argv) : void +{static} Initialize(std::string const&) : void + +{static} InitializeRuntimeAddonConfiguration(const runtime::RuntimeConfiguration& runtime_configuration) : Result + +{static} InitializeRuntimeAddonConfiguration(score::json::Any json) : Result +{static} getInstance() : Runtime& +Runtime(Configuration&& config) +resolve(const InstanceSpecifier&) : std::vector +getBindingRuntime(BindingType binding) : IBindingRuntime* + -MergeAdditionalConfiguration(const Configuration& additional_configuration) : Result .. Notes: Runtime is not copyable. diff --git a/score/mw/com/doc/user_facing_API_examples.md b/score/mw/com/doc/user_facing_API_examples.md index caea21437..616e21f74 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) | @@ -272,7 +273,45 @@ 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); + + // Ensure that runtime with combined configuration is loaded + Runtime::getInstance(); + + 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: diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 62d5437db..95f9ca41e 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -1026,6 +1026,7 @@ cc_library( "//score/mw/com/impl/plumbing:runtime", "//score/mw/com/impl/tracing/configuration:tracing_filter_config", "@score_baselibs//score/concurrency:long_running_threads_container", + "@score_baselibs//score/filesystem", "@score_baselibs//score/language/futurecpp", ], ) @@ -1158,6 +1159,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/bindings/lola/runtime_test.cpp b/score/mw/com/impl/bindings/lola/runtime_test.cpp index 15bbdef74..25954dd71 100644 --- a/score/mw/com/impl/bindings/lola/runtime_test.cpp +++ b/score/mw/com/impl/bindings/lola/runtime_test.cpp @@ -62,8 +62,10 @@ class RuntimeFixture : public ::testing::Test { unit_.reset(nullptr); config_.reset(nullptr); - config_ = std::make_unique( - service_types, service_instances, std::move(global_configuration), std::move(tracing_configuration)); + config_ = std::make_unique(service_types, + ServiceInstancesContainer{service_instances}, + std::move(global_configuration), + std::move(tracing_configuration)); tracing_runtime_ = std::make_unique(0, *config_); @@ -166,7 +168,7 @@ TEST_F(RuntimeFixture, GetMessagePassingCfgWithPredefinedTwoLolaServiceConfig) global_configuration.SetProcessAsilLevel(QualityType::kASIL_B); Configuration configuration{Configuration::ServiceTypeDeployments{}, - instanceDeployments, + ServiceInstancesContainer{instanceDeployments}, std::move(global_configuration), TracingConfiguration{}}; @@ -238,7 +240,7 @@ TEST_F(RuntimeFixture, GetMessagePassingCfgOneEmptyQMProvider) global_configuration.SetProcessAsilLevel(QualityType::kASIL_B); Configuration configuration{Configuration::ServiceTypeDeployments{}, - instanceDeployments, + ServiceInstancesContainer{instanceDeployments}, std::move(global_configuration), TracingConfiguration{}}; @@ -308,7 +310,7 @@ TEST_F(RuntimeFixture, GetMessagePassingCfgOneEmptyQMConsumer) global_configuration.SetProcessAsilLevel(QualityType::kASIL_B); Configuration configuration{Configuration::ServiceTypeDeployments{}, - instanceDeployments, + ServiceInstancesContainer{instanceDeployments}, std::move(global_configuration), TracingConfiguration{}}; @@ -345,7 +347,7 @@ TEST_F(RuntimeFixture, GetMessagePassingCfgWithNoServiceInstances) global_configuration.SetReceiverMessageQueueSize(QualityType::kASIL_QM, kExpectedQmQueueSize); Configuration configuration{Configuration::ServiceTypeDeployments{}, - Configuration::ServiceInstanceDeployments{}, + ServiceInstancesContainer{Configuration::ServiceInstanceDeployments{}}, std::move(global_configuration), TracingConfiguration{}}; @@ -377,7 +379,7 @@ TEST_F(RuntimeFixture, GetMessagePassingCfgMissingConsumer) GlobalConfiguration global_configuration{}; Configuration configuration{Configuration::ServiceTypeDeployments{}, - instanceDeployments, + ServiceInstancesContainer{instanceDeployments}, std::move(global_configuration), TracingConfiguration{}}; @@ -407,7 +409,7 @@ TEST_F(RuntimeDeathTest, GettingAsilBConfigInQmProcessTerminates) global_configuration.SetProcessAsilLevel(QualityType::kASIL_QM); Configuration configuration{Configuration::ServiceTypeDeployments{}, - instanceDeployments, + ServiceInstancesContainer{instanceDeployments}, std::move(global_configuration), TracingConfiguration{}}; diff --git a/score/mw/com/impl/bindings/lola/tracing/tracing_runtime.cpp b/score/mw/com/impl/bindings/lola/tracing/tracing_runtime.cpp index c6dee7164..aa973423c 100644 --- a/score/mw/com/impl/bindings/lola/tracing/tracing_runtime.cpp +++ b/score/mw/com/impl/bindings/lola/tracing/tracing_runtime.cpp @@ -258,7 +258,6 @@ void TracingRuntime::ClearCachedFileDescriptorForReregisteringShmObject( analysis::tracing::ServiceInstanceElement TracingRuntime::ConvertToTracingServiceInstanceElement( const impl::tracing::ServiceElementInstanceIdentifierView service_element_instance_identifier_view) const noexcept { - const auto& service_instance_deployments = configuration_.GetServiceInstances(); const auto& service_type_deployments = configuration_.GetServiceTypes(); // @todo: Replace the configuration unordered_maps with maps and use CompareId? @@ -269,7 +268,7 @@ analysis::tracing::ServiceInstanceElement TracingRuntime::ConvertToTracingServic // The instance specifier is loaded into the configuration during the initialization, so the container // will always have an element with the specified key. // coverity[autosar_cpp14_a15_4_2_violation] - const auto& service_instance_deployment = service_instance_deployments.at(instance_specifier); + const auto& service_instance_deployment = configuration_.GetServiceInstances().at(instance_specifier); auto* lola_service_instance_deployment = std::get_if(&service_instance_deployment.bindingInfo_); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(lola_service_instance_deployment != nullptr); diff --git a/score/mw/com/impl/bindings/lola/tracing/tracing_runtime_test.cpp b/score/mw/com/impl/bindings/lola/tracing/tracing_runtime_test.cpp index 9b910f8c4..167ead900 100644 --- a/score/mw/com/impl/bindings/lola/tracing/tracing_runtime_test.cpp +++ b/score/mw/com/impl/bindings/lola/tracing/tracing_runtime_test.cpp @@ -64,7 +64,7 @@ using testing::Return; using testing::WithArg; const Configuration kEmptyConfiguration{Configuration::ServiceTypeDeployments{}, - Configuration::ServiceInstanceDeployments{}, + ServiceInstancesContainer{Configuration::ServiceInstanceDeployments{}}, GlobalConfiguration{}, TracingConfiguration{}}; @@ -751,7 +751,7 @@ class RegisterWithGenericTraceApiFixture : public testing::Test TracingConfiguration dummy_tracing_configuration{}; dummy_tracing_configuration.SetApplicationInstanceID(application_instance_id_); return std::make_unique(Configuration::ServiceTypeDeployments{}, - Configuration::ServiceInstanceDeployments{}, + ServiceInstancesContainer{Configuration::ServiceInstanceDeployments{}}, GlobalConfiguration{}, std::move(dummy_tracing_configuration)); } @@ -1032,13 +1032,14 @@ TEST_F(TracingRuntimeConvertToTracingServiceInstanceElementFixture, const auto lola_service_type_deployment = CreateTypeDeployment(service_id_, {{event_name_, event_id_}}, {{field_name_, field_id_}}); - Configuration configuration{ - {{service_identifier_type, ServiceTypeDeployment{lola_service_type_deployment}}}, - {{instance_specifier_, - ServiceInstanceDeployment{ - service_identifier_type, lola_service_instance_deployment, QualityType::kInvalid, instance_specifier_}}}, - GlobalConfiguration{}, - TracingConfiguration{}}; + Configuration configuration{{{service_identifier_type, ServiceTypeDeployment{lola_service_type_deployment}}}, + ServiceInstancesContainer{{{instance_specifier_, + ServiceInstanceDeployment{service_identifier_type, + lola_service_instance_deployment, + QualityType::kInvalid, + instance_specifier_}}}}, + GlobalConfiguration{}, + TracingConfiguration{}}; const ServiceInstanceElement expected_service_instance_element_event{ static_cast(service_id_), @@ -1084,14 +1085,15 @@ TEST_F(TracingRuntimeConvertToTracingServiceInstanceElementDeathTest, const auto lola_service_type_deployment = CreateTypeDeployment(service_id_, {{event_name_, event_id_}}, {{field_name_, field_id_}}); - Configuration configuration{{{service_identifier_type, ServiceTypeDeployment{lola_service_type_deployment}}}, - {{instance_specifier_, - ServiceInstanceDeployment{service_identifier_type, - lola_service_instance_deployment_without_instance_id, - QualityType::kInvalid, - instance_specifier_}}}, - GlobalConfiguration{}, - TracingConfiguration{}}; + Configuration configuration{ + {{service_identifier_type, ServiceTypeDeployment{lola_service_type_deployment}}}, + ServiceInstancesContainer{{{instance_specifier_, + ServiceInstanceDeployment{service_identifier_type, + lola_service_instance_deployment_without_instance_id, + QualityType::kInvalid, + instance_specifier_}}}}, + GlobalConfiguration{}, + TracingConfiguration{}}; // Given a TracingRuntimeObject with a provided configuration object which does not contain an instance id TracingRuntime tracing_runtime{kNumberOfTotalConfiguredTracingSlots, configuration}; @@ -1114,13 +1116,14 @@ TEST_F(TracingRuntimeConvertToTracingServiceInstanceElementDeathTest, const auto lola_service_type_deployment = CreateTypeDeployment(service_id_, {{event_name_, event_id_}}, {{field_name_, field_id_}}); - Configuration configuration{ - {{service_identifier_type, ServiceTypeDeployment{lola_service_type_deployment}}}, - {{instance_specifier_, - ServiceInstanceDeployment{ - service_identifier_type, lola_service_instance_deployment, QualityType::kInvalid, instance_specifier_}}}, - GlobalConfiguration{}, - TracingConfiguration{}}; + Configuration configuration{{{service_identifier_type, ServiceTypeDeployment{lola_service_type_deployment}}}, + ServiceInstancesContainer{{{instance_specifier_, + ServiceInstanceDeployment{service_identifier_type, + lola_service_instance_deployment, + QualityType::kInvalid, + instance_specifier_}}}}, + GlobalConfiguration{}, + TracingConfiguration{}}; // Given a TracingRuntimeObject with a provided configuration object TracingRuntime tracing_runtime{kNumberOfTotalConfiguredTracingSlots, configuration}; diff --git a/score/mw/com/impl/configuration/BUILD b/score/mw/com/impl/configuration/BUILD index 7a3e60705..ed147b8b6 100644 --- a/score/mw/com/impl/configuration/BUILD +++ b/score/mw/com/impl/configuration/BUILD @@ -130,9 +130,11 @@ cc_library( "//score/mw/com/impl/configuration:__subpackages__", ], deps = [ + ":configuration_error", ":global_configuration", ":service_identifier_type", ":service_instance_deployment", + ":service_instances_container", ":service_type_deployment", ":tracing_configuration", "//score/mw/com/impl:instance_specifier", @@ -438,6 +440,20 @@ cc_library( ], ) +cc_library( + name = "service_instances_container", + srcs = ["service_instances_container.cpp"], + hdrs = ["service_instances_container.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + deps = [ + ":configuration_error", + ":service_identifier_type", + ":service_instance_deployment", + "@score_baselibs//score/result", + ], +) + cc_library( name = "configuration", features = COMPILER_WARNING_FEATURES, @@ -456,6 +472,7 @@ cc_library( ":service_identifier_type", ":service_instance_deployment", ":service_instance_id", + ":service_instances_container", ":service_type_deployment", ":service_version_type", ":shm_size_calc_mode", @@ -709,6 +726,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/config_validate.cpp b/score/mw/com/impl/configuration/config_validate.cpp index 0eef1402e..975b6ad25 100644 --- a/score/mw/com/impl/configuration/config_validate.cpp +++ b/score/mw/com/impl/configuration/config_validate.cpp @@ -73,6 +73,18 @@ InstanceSpecifier CreateValidInstanceSpecifier(std::string instance_specifier_na void CrosscheckAsilLevels(const Configuration& config) { + + // config.GetServiceInstances().for_each([&config](const auto& instance_specifier, const auto& instance_deployment) + // { + // if ((instance_deployment.asilLevel_ == QualityType::kASIL_B) && + // (config.GetGlobalConfiguration().GetProcessAsilLevel() != QualityType::kASIL_B)) + // { + // ::score::mw::log::LogFatal("lola") + // << "Service instance has a higher ASIL than the process. This is invalid, terminating"; + // SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(false); + // } + // }); + for (const auto& service_instance : config.GetServiceInstances()) { if ((service_instance.second.asilLevel_ == QualityType::kASIL_B) && diff --git a/score/mw/com/impl/configuration/configuration.cpp b/score/mw/com/impl/configuration/configuration.cpp index 1cf6fc3b5..e943f7e14 100644 --- a/score/mw/com/impl/configuration/configuration.cpp +++ b/score/mw/com/impl/configuration/configuration.cpp @@ -12,6 +12,7 @@ ********************************************************************************/ #include "score/mw/com/impl/configuration/configuration.h" +#include "score/mw/com/impl/configuration/configuration_error.h" #include "score/mw/log/logging.h" #include @@ -21,7 +22,7 @@ namespace score::mw::com::impl { Configuration::Configuration(ServiceTypeDeployments service_types, - ServiceInstanceDeployments service_instances, + ServiceInstancesContainer service_instances, GlobalConfiguration global_configuration, TracingConfiguration tracing_configuration) noexcept : service_types_{std::move(service_types)}, @@ -60,4 +61,21 @@ ServiceInstanceDeployment* Configuration::AddServiceInstanceDeployments( return &emplace_result.first->second; } +Result Configuration::MergeServiceEntries(const Configuration& additional_configuration) noexcept +{ + for (auto& service_type : additional_configuration.service_types_) + { + for (const auto& existing_service_type : service_types_) + { + if (existing_service_type.first.ToString() == service_type.first.ToString()) + { + return Unexpected(MakeError(configuration_errc::configuration_merge_duplicate_service_type)); + } + } + + std::ignore = service_types_.emplace(std::move(service_type.first), std::move(service_type.second)); + } + + return service_instances_.MergeServiceEntries(additional_configuration.service_instances_); +} } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/configuration/configuration.h b/score/mw/com/impl/configuration/configuration.h index e7d6f1528..a5a1f2f8f 100644 --- a/score/mw/com/impl/configuration/configuration.h +++ b/score/mw/com/impl/configuration/configuration.h @@ -16,9 +16,11 @@ #include "score/mw/com/impl/configuration/global_configuration.h" #include "score/mw/com/impl/configuration/service_identifier_type.h" #include "score/mw/com/impl/configuration/service_instance_deployment.h" +#include "score/mw/com/impl/configuration/service_instances_container.h" #include "score/mw/com/impl/configuration/service_type_deployment.h" #include "score/mw/com/impl/configuration/tracing_configuration.h" #include "score/mw/com/impl/instance_specifier.h" +#include "score/result/result.h" #include #include @@ -48,7 +50,7 @@ class Configuration final using ServiceInstanceDeployments = std::unordered_map; Configuration(ServiceTypeDeployments service_types, - ServiceInstanceDeployments service_instances, + ServiceInstancesContainer service_instances, GlobalConfiguration global_configuration, TracingConfiguration tracing_configuration) noexcept; ~Configuration() noexcept = default; @@ -67,11 +69,17 @@ class Configuration final InstanceSpecifier instance_specifier, ServiceInstanceDeployment service_instance_deployment) noexcept; + /// \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; + const ServiceTypeDeployments& GetServiceTypes() const& noexcept { return service_types_; } - const ServiceInstanceDeployments& GetServiceInstances() const& noexcept + const ServiceInstancesContainer& GetServiceInstances() const& noexcept { return service_instances_; } @@ -92,7 +100,7 @@ class Configuration final * Value is the ServiceIdentifierType, the port is typed with. */ ServiceTypeDeployments service_types_; - ServiceInstanceDeployments service_instances_; + ServiceInstancesContainer service_instances_; GlobalConfiguration global_configuration_; TracingConfiguration tracing_configuration_; }; diff --git a/score/mw/com/impl/configuration/configuration_error.h b/score/mw/com/impl/configuration/configuration_error.h index 759f4fd6e..540946216 100644 --- a/score/mw/com/impl/configuration/configuration_error.h +++ b/score/mw/com/impl/configuration/configuration_error.h @@ -27,6 +27,8 @@ enum class configuration_errc : score::result::ErrorCode serialization_deploymentinformation_invalid = 0, serialization_no_shmbindinginformation = 1, serialization_shmbindinginformation_invalid = 2, + configuration_merge_duplicate_service_type = 3, + configuration_merge_duplicate_service_instance = 4, }; /// \brief See above explanation in configuration_errc @@ -59,6 +61,13 @@ class ConfigurationErrorDomain final : public score::result::ErrorDomain case static_cast(configuration_errc::serialization_shmbindinginformation_invalid): return "serialization of is invalid"; // 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] 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..6d3fc3afc 100644 --- a/score/mw/com/impl/configuration/configuration_error_test.cpp +++ b/score/mw/com/impl/configuration/configuration_error_test.cpp @@ -50,6 +50,18 @@ TEST_F(ConfigurationErrorTest, MessageForSerializationShmbindinginformationInval "serialization of is invalid"); } +TEST_F(ConfigurationErrorTest, MessageForMergeErrorDuplicateServiceType) +{ + testErrorMessage(configuration_errc::configuration_merge_duplicate_service_type, + "Duplicate service type was found during configuration merge. Merge aborted."); +} + +TEST_F(ConfigurationErrorTest, MessageForMergeErrorDuplicateServiceInstance) +{ + testErrorMessage(configuration_errc::configuration_merge_duplicate_service_instance, + "Duplicate service instance was found during configuration merge. Merge aborted."); +} + 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 9946edbe1..2bc939a5a 100644 --- a/score/mw/com/impl/configuration/configuration_json_parsing_strategy.cpp +++ b/score/mw/com/impl/configuration/configuration_json_parsing_strategy.cpp @@ -1231,7 +1231,8 @@ Configuration ConfigurationJsonParsingStrategy::Parse(score::json::Any json) con auto tracing_configuration = ParseTracingProperties(json_map); auto service_type_deployments = ParseServiceTypes(json_map); - auto service_instance_deployments = ParseServiceInstances(json_map, tracing_configuration); + auto service_instance_deployments = + ServiceInstancesContainer{ParseServiceInstances(json_map, tracing_configuration)}; auto global_configuration = ParseGlobalProperties(json_map); Configuration configuration{std::move(service_type_deployments), 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 9b0a55671..2b6dcd2ad 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 @@ -2544,11 +2544,10 @@ TEST(ConfigurationJsonParsingStrategy, StrictPermissionIsSet) // When parsing the JSON const auto configuration = score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2)); - const auto instances = configuration.GetServiceInstances(); - ASSERT_FALSE(instances.empty()); + ASSERT_FALSE(configuration.GetServiceInstances().empty()); // That LolaServiceInstanceDeployment instance is obtained - const auto deployment = instances.begin()->second; + const auto deployment = configuration.GetServiceInstances().begin()->second; const auto* const lola_service_instance = std::get_if(&deployment.bindingInfo_); ASSERT_NE(lola_service_instance, nullptr); // And "permission-checks" attribute is set to "strict" @@ -2610,11 +2609,10 @@ TEST(ConfigurationJsonParsingStrategy, GetNoneStrictIfNoPermissionFlagAttr) // When parsing the JSON const auto configuration = score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(j2)); - const auto instances = configuration.GetServiceInstances(); - ASSERT_FALSE(instances.empty()); + ASSERT_FALSE(configuration.GetServiceInstances().empty()); // That LolaServiceInstanceDeployment instance is obtained - const auto deployment = instances.begin()->second; + const auto deployment = configuration.GetServiceInstances().begin()->second; const auto* const lola_service_instance = std::get_if(&deployment.bindingInfo_); ASSERT_NE(lola_service_instance, nullptr); // And "permission-checks" attribute is set to none-"strict" @@ -3656,9 +3654,8 @@ TEST(TracingFilterConfigGetNumberOfTraceingSlots, CorrectlyParseAJsonContainingN // When json is parsed into the configuration auto config = score::mw::com::impl::configuration::ConfigurationJsonParsingStrategy{}.Parse(std::move(config_json)); - auto serv_inst_depls = config.GetServiceInstances(); const auto instance_specifier = InstanceSpecifier::Create(std::string{instance_specifier_str}).value(); - const auto serv_inst_depl_it = serv_inst_depls.at(instance_specifier); + const auto serv_inst_depl_it = config.GetServiceInstances().at(instance_specifier); const auto lola_service_instance_depl = std::get<0>(serv_inst_depl_it.bindingInfo_); const auto& field = lola_service_instance_depl.fields_.at(field_name_str); diff --git a/score/mw/com/impl/configuration/configuration_test.cpp b/score/mw/com/impl/configuration/configuration_test.cpp index 5a948e86a..baa7082b4 100644 --- a/score/mw/com/impl/configuration/configuration_test.cpp +++ b/score/mw/com/impl/configuration/configuration_test.cpp @@ -31,6 +31,8 @@ #include #include +#include "configuration_error.h" + namespace score::mw::com::impl { namespace @@ -57,7 +59,7 @@ class ConfigurationFixture : public ::testing::Test instance_deployments.emplace(kConfigStoreQm.instance_specifier_, *kConfigStoreQm.service_instance_deployment_); unit_.emplace(std::move(type_deployments), - std::move(instance_deployments), + ServiceInstancesContainer{std::move(instance_deployments)}, GlobalConfiguration{}, TracingConfiguration{}); } @@ -65,7 +67,7 @@ class ConfigurationFixture : public ::testing::Test void WithEmptyConfiguration() { unit_.emplace(Configuration::ServiceTypeDeployments{}, - Configuration::ServiceInstanceDeployments{}, + ServiceInstancesContainer{Configuration::ServiceInstanceDeployments{}}, GlobalConfiguration{}, TracingConfiguration{}); } @@ -109,7 +111,7 @@ TEST_F(ConfigurationFixture, construct) // verify that unit2 really contains still valid copies EXPECT_EQ(unit2.GetServiceTypes().size(), 1); EXPECT_EQ(unit2.GetServiceInstances().size(), 1); - EXPECT_NE(unit2.GetServiceInstances().find(kConfigStoreQm.instance_specifier_), unit2.GetServiceInstances().end()); + EXPECT_TRUE(unit2.GetServiceInstances().find(kConfigStoreQm.instance_specifier_).has_value()); // verify default values of global section EXPECT_EQ(unit2.GetGlobalConfiguration().GetProcessAsilLevel(), QualityType::kASIL_QM); @@ -277,6 +279,152 @@ TEST_F(ConfigurationFixture, EXPECT_EQ(*service_instance_deployment_ptr, *kConfigStoreQm.service_instance_deployment_); } +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, + ServiceInstancesContainer{std::move(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().GetServiceTypes().size(), 2); + EXPECT_EQ(unit_.value().GetServiceInstances().size(), 2); +} + +TEST_F(ConfigurationFixture, MergingIntoEmptyConfigurationLeadsToResultingConfigEqualsIncomingConfig) +{ + // Given an empty configuration ... + WithEmptyConfiguration(); + + // TODO How to fix those + // // ... 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, ServiceInstancesContainer{std::move(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(unit_.value().GetServiceTypes(), type_deployments); + // EXPECT_EQ(unit_.value().GetServiceInstances(), instance_deployments); +} + +TEST_F(ConfigurationFixture, MergingEmptyConfigurationLeadsToResultingConfigEqualsInitialConfig) +{ + // Given a configuration with some entries + WithMinimalConfiguration(); + + // TODO How to fix those + // const auto type_deployments_backup = unit_.value().GetServiceTypes(); + // const auto instance_deployments_backup = unit_.value().GetServiceInstances(); + // + // // ... and an empty configuration that shall be merged + // auto addon_configuration = Configuration{Configuration::ServiceTypeDeployments{}, + // ServiceInstancesContainer{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(unit_.value().GetServiceTypes(), type_deployments_backup); + // EXPECT_EQ(unit_.value().GetServiceInstances(), 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, + ServiceInstancesContainer{std::move(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, + ServiceInstancesContainer{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); +} + 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..9e1b8e532 --- /dev/null +++ b/score/mw/com/impl/configuration/example/mw_com_config_to_merge.json @@ -0,0 +1,114 @@ +{ + "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 + } + ] + } + ], + "global": { + "asil-level": "B", + "applicationID": 1234, + "queue-size": { + "QM-receiver": 8, + "B-receiver": 5, + "B-sender": 12 + }, + "shm-size-calc-mode": "SIMULATION" + }, + "tracing": { + "enable": true, + "applicationInstanceID": "ara_com_example", + "traceFilterConfigPath": "./mw_com_trace_filter.json" + } +} 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..8a9bdf34c --- /dev/null +++ b/score/mw/com/impl/configuration/example/mw_com_config_to_merge_second.json @@ -0,0 +1,102 @@ +{ + "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 + } + ] + } + ], + "global": { + "asil-level": "B", + "applicationID": 1234, + "queue-size": { + "QM-receiver": 8, + "B-receiver": 5, + "B-sender": 12 + }, + "shm-size-calc-mode": "SIMULATION" + }, + "tracing": { + "enable": true, + "applicationInstanceID": "ara_com_example", + "traceFilterConfigPath": "./mw_com_trace_filter.json" + } +} \ No newline at end of file diff --git a/score/mw/com/impl/configuration/service_instances_container.cpp b/score/mw/com/impl/configuration/service_instances_container.cpp new file mode 100644 index 000000000..f03eee301 --- /dev/null +++ b/score/mw/com/impl/configuration/service_instances_container.cpp @@ -0,0 +1,2 @@ + +#include "score/mw/com/impl/configuration/service_instances_container.h" diff --git a/score/mw/com/impl/configuration/service_instances_container.h b/score/mw/com/impl/configuration/service_instances_container.h new file mode 100644 index 000000000..b0d421764 --- /dev/null +++ b/score/mw/com/impl/configuration/service_instances_container.h @@ -0,0 +1,120 @@ +#ifndef COMMUNICATION_SERVICE_INSTANCES_CONTAINER_H +#define COMMUNICATION_SERVICE_INSTANCES_CONTAINER_H + +#include "score/mw/com/impl/configuration/service_instance_deployment.h" +#include "score/mw/com/impl/instance_specifier.h" + +#include "score/result/result.h" + +#include +#include +#include +#include +#include + +#include "configuration_error.h" + +namespace score::mw::com::impl +{ + +class ServiceInstancesContainer +{ + + using ServiceInstanceDeployments = std::unordered_map; + + public: + explicit ServiceInstancesContainer(ServiceInstanceDeployments service_instances) + : service_instances_{std::move(service_instances)} + { + } + + ServiceInstancesContainer() = default; + + ServiceInstancesContainer(const ServiceInstancesContainer& other) = delete; + ServiceInstancesContainer(ServiceInstancesContainer&& other) noexcept + : service_instances_{std::move(other.service_instances_)} + { + } + ServiceInstancesContainer& operator=(const ServiceInstancesContainer&) = delete; + ServiceInstancesContainer& operator=(ServiceInstancesContainer&&) = delete; + + ServiceInstanceDeployment at(const InstanceSpecifier& specifier) const + { + std::lock_guard lock_instances{instances_mutex_}; + return service_instances_.at(specifier); + } + + typename ServiceInstanceDeployments::const_iterator begin() const + { + std::lock_guard lock_instances{instances_mutex_}; + return service_instances_.begin(); + } + + typename ServiceInstanceDeployments::const_iterator end() const + { + std::lock_guard lock_instances{instances_mutex_}; + return service_instances_.end(); + } + + std::optional> find( + const InstanceSpecifier& specifier) const + { + std::lock_guard lock_instances{instances_mutex_}; + const auto it = service_instances_.find(specifier); + if (it == service_instances_.end()) + { + return std::nullopt; + } + return std::cref(it->second); + } + + std::size_t size() const + { + std::lock_guard lock_instances{instances_mutex_}; + return service_instances_.size(); + } + + bool empty() const + { + std::lock_guard lock_instances{instances_mutex_}; + return service_instances_.empty(); + } + + Result MergeServiceEntries(const ServiceInstanceDeployments& other_instances) noexcept + { + std::lock_guard lock_instances{instances_mutex_}; + for (const auto& service_instance : other_instances) + { + for (const auto& existing_service_instance : service_instances_) + { + if (existing_service_instance.first.ToString() == service_instance.first.ToString()) + { + return Unexpected(MakeError(configuration_errc::configuration_merge_duplicate_service_instance)); + } + } + std::ignore = service_instances_.emplace(service_instance.first, service_instance.second); + } + return {}; + } + + Result MergeServiceEntries(const ServiceInstancesContainer& other) noexcept + { + std::lock_guard lock_other{other.instances_mutex_}; + return MergeServiceEntries(other.service_instances_); + } + + template + std::pair emplace(Args&&... args) + { + std::lock_guard lock_instances{instances_mutex_}; + return service_instances_.emplace(std::forward(args)...); + } + + private: + ServiceInstanceDeployments service_instances_; + mutable std::mutex instances_mutex_; +}; + +} // namespace score::mw::com::impl + +#endif // COMMUNICATION_SERVICE_INSTANCES_CONTAINER_H diff --git a/score/mw/com/impl/instance_identifier_test.cpp b/score/mw/com/impl/instance_identifier_test.cpp index 2494181c8..b206d8c5d 100644 --- a/score/mw/com/impl/instance_identifier_test.cpp +++ b/score/mw/com/impl/instance_identifier_test.cpp @@ -53,7 +53,7 @@ class ConfigurationGuard private: Configuration configuration{Configuration::ServiceTypeDeployments{}, - Configuration::ServiceInstanceDeployments{}, + ServiceInstancesContainer{Configuration::ServiceInstanceDeployments{}}, GlobalConfiguration{}, TracingConfiguration{}}; }; @@ -322,7 +322,7 @@ TEST_F(InstanceIdentifierFixture, CreateStoresDeploymentsIntoConfiguredConfigura // Given an empty Configuration registered as the InstanceIdentifier configuration Configuration configuration{Configuration::ServiceTypeDeployments{}, - Configuration::ServiceInstanceDeployments{}, + ServiceInstancesContainer{Configuration::ServiceInstanceDeployments{}}, GlobalConfiguration{}, TracingConfiguration{}}; InstanceIdentifierAttorney::SetConfiguration(&configuration); @@ -351,9 +351,9 @@ TEST_F(InstanceIdentifierFixture, CreateStoresDeploymentsIntoConfiguredConfigura const auto& service_instances = configuration.GetServiceInstances(); ASSERT_EQ(service_instances.size(), 1); - const auto instance_it = service_instances.find(kInstanceSpecifier1); - ASSERT_NE(instance_it, service_instances.cend()); - ExpectServiceInstanceDeploymentObjectsEqual(instance_it->second, service_instance_deployment); + const auto instance = service_instances.find(kInstanceSpecifier1); + ASSERT_TRUE(instance.has_value()); + ExpectServiceInstanceDeploymentObjectsEqual(instance.value().get(), service_instance_deployment); InstanceIdentifierAttorney::SetConfiguration(nullptr); } diff --git a/score/mw/com/impl/plumbing/binding_runtime_factory_test.cpp b/score/mw/com/impl/plumbing/binding_runtime_factory_test.cpp index afeea30df..3ba46890a 100644 --- a/score/mw/com/impl/plumbing/binding_runtime_factory_test.cpp +++ b/score/mw/com/impl/plumbing/binding_runtime_factory_test.cpp @@ -59,10 +59,11 @@ class BindingRuntimeFactoryFixture : public ::testing::Test Configuration::ServiceInstanceDeployments service_instance_deployments{ {kConfigStoreQm1.instance_specifier_, *kConfigStoreQm1.service_instance_deployment_}, }; - configuration_ = std::make_unique(std::move(service_type_deployments), - std::move(service_instance_deployments), - GlobalConfiguration{}, - TracingConfiguration{}); + configuration_ = + std::make_unique(std::move(service_type_deployments), + ServiceInstancesContainer{std::move(service_instance_deployments)}, + GlobalConfiguration{}, + TracingConfiguration{}); return *this; } @@ -76,10 +77,11 @@ class BindingRuntimeFactoryFixture : public ::testing::Test {kConfigStoreQm1.instance_specifier_, *kConfigStoreQm1.service_instance_deployment_}, {kConfigStoreQm2.instance_specifier_, *kConfigStoreQm2.service_instance_deployment_}, }; - configuration_ = std::make_unique(std::move(service_type_deployments), - std::move(service_instance_deployments), - GlobalConfiguration{}, - TracingConfiguration{}); + configuration_ = + std::make_unique(std::move(service_type_deployments), + ServiceInstancesContainer{std::move(service_instance_deployments)}, + GlobalConfiguration{}, + TracingConfiguration{}); return *this; } @@ -94,10 +96,11 @@ class BindingRuntimeFactoryFixture : public ::testing::Test {kConfigStoreQm1.instance_specifier_, InstanceIdentifierView{instance_identifier}.GetServiceInstanceDeployment()}, }; - configuration_ = std::make_unique(std::move(service_type_deployments), - std::move(service_instance_deployments), - GlobalConfiguration{}, - TracingConfiguration{}); + configuration_ = + std::make_unique(std::move(service_type_deployments), + ServiceInstancesContainer{std::move(service_instance_deployments)}, + GlobalConfiguration{}, + TracingConfiguration{}); return *this; } diff --git a/score/mw/com/impl/runtime.cpp b/score/mw/com/impl/runtime.cpp index 35274d5c4..383257626 100644 --- a/score/mw/com/impl/runtime.cpp +++ b/score/mw/com/impl/runtime.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -116,7 +117,6 @@ void Runtime::Initialize(const runtime::RuntimeConfiguration& runtime_configurat error_double_init(); return; } - if (initialization_config_.has_value()) { warn_double_init(); @@ -126,6 +126,37 @@ void Runtime::Initialize(const runtime::RuntimeConfiguration& runtime_configurat 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(std::move(config)); +} + +Result Runtime::InitializeRuntimeAddonConfiguration(score::json::Any json) +{ + auto config = configuration::Parse(std::move(json)); + + return HandleAddonConfiguration(std::move(config)); +} + +Result Runtime::HandleAddonConfiguration(const Configuration& config) noexcept +{ + 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() noexcept -> IRuntime& { if (mock_ != nullptr) @@ -218,19 +249,20 @@ Runtime::~Runtime() noexcept std::vector Runtime::resolve(const InstanceSpecifier& specifier) const { + std::lock_guard lock{configuration_mutex_}; std::vector result; const auto instanceSearch = configuration_.GetServiceInstances().find(specifier); - if (instanceSearch != configuration_.GetServiceInstances().end()) + if (instanceSearch.has_value()) { // @todo: Right now we don't support multi-binding, if we do, we need to have some kind of loop - const auto type_deployment = configuration_.GetServiceTypes().find(instanceSearch->second.service_); + const auto type_deployment = configuration_.GetServiceTypes().find(instanceSearch.value().get().service_); // LCOV_EXCL_BR_START defensive programming: The configuration parser ensures that if a matching service // instance is available, there is also a matching service type available. Because parsing of the configuration // is automatically done before instantiating the runtime, this condition is always positive. To increase the // robustness of the code, we still check for this condition. if (type_deployment != configuration_.GetServiceTypes().cend()) { - result.push_back(make_InstanceIdentifier(instanceSearch->second, type_deployment->second)); + result.push_back(make_InstanceIdentifier(instanceSearch.value().get(), type_deployment->second)); } else { @@ -244,6 +276,18 @@ std::vector Runtime::resolve(const InstanceSpecifier& specif return result; } +Result Runtime::MergeAdditionalConfiguration(const Configuration& additional_configuration) noexcept +{ + std::lock_guard lock{configuration_mutex_}; + const auto merge_result = configuration_.MergeServiceEntries(std::move(additional_configuration)); + if (!merge_result.has_value()) + { + return merge_result; + } + InstanceIdentifier::SetConfiguration(&configuration_); + 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 ced0e6383..bd601b705 100644 --- a/score/mw/com/impl/runtime.h +++ b/score/mw/com/impl/runtime.h @@ -72,10 +72,32 @@ class Runtime final : public IRuntime /// \brief static initializer for the runtime. Must be called once per process, which intends to use ara::com /// functionality. /// \attention Multiple calls to one of the Initialize() overloads shall be avoided. They may have no effect after - /// once our Runtime singleton has been created via getInstance()/getInstanceInternal() + /// once our Runtime singleton has been created via getInstance()/getInstanceInternal(). + /// This call might lead to a std::terminate() if it has been detected that an add-on configuration has + /// already been loaded. /// \param runtime_configuration object containing configuration needed to initialize the Runtime static void Initialize(const runtime::RuntimeConfiguration& runtime_configuration); + /// \brief Initializes or 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. This configuration will be used as the initial one if there is no other configuration + /// loaded yet. + /// \attention This function may call std::terminate() in case that the configuration is incompatible to the + /// previously + /// loaded one or if there is a mw::com configuration in the default path, but it has not yet been + /// loaded. + /// \param runtime_configuration object containing configuration needed to initialize the Runtime + 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 may call std::terminate() in case that the configuration is incompatible to the + /// previously loaded one + /// \param json The JSON object containing the add-on configuration. + 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,12 +153,23 @@ class Runtime final : public IRuntime /// \pre the internal static initialization_config_ has to be initialized with a Configuration. static Runtime& getInstanceInternal() noexcept; + /// \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 this 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_; /// \brief mutex to synchronize potentially concurrent calls to initialization logic of Runtime. static std::mutex mutex_; + /// \brief mutex to synchronize access to the configuration_ member of the Runtime singleton instance. + mutable std::mutex configuration_mutex_; + /// \brief flag, whether the runtime is now locked in the sense, that the singleton has been already created /// with a given configuration. static bool runtime_initialization_locked_; diff --git a/score/mw/com/impl/runtime_single_exec_test.cpp b/score/mw/com/impl/runtime_single_exec_test.cpp index 9cbeb0caa..8324c613f 100644 --- a/score/mw/com/impl/runtime_single_exec_test.cpp +++ b/score/mw/com/impl/runtime_single_exec_test.cpp @@ -31,11 +31,15 @@ #include #include #include +#include #include #include +#include #include #include +#include "configuration/configuration_error.h" + namespace score::mw::com::impl { @@ -94,7 +98,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 @@ -130,6 +135,13 @@ void WithConfigAtDefaultPath(const std::string& source_path) ASSERT_TRUE(filesystem.CopyFile(filesystem::Path{source_path}, target).has_value()); } +void WithNoFileAtDefaultPath() +{ + auto& filesystem = filesystem::IStandardFilesystem::instance(); + filesystem::Path dir{"etc"}; + score::cpp::ignore = filesystem.RemoveAll(dir); +} + using RuntimeInitializationTest = RuntimeSingleTestPerProcessFixture; TEST_F(RuntimeInitializationTest, ConstructorRegistersItsOwnConfigurationWithInstanceIdentifier) @@ -263,6 +275,109 @@ TEST_F(RuntimeInitializationTest, ImplicitInitializationLoadsCorrectConfiguratio }); } +TEST_F(RuntimeInitializationTest, ConfigurationGetsMergedAndLoadedIfInitialConfigurationHasBeenLoadedEarlier) +{ + TestInSeparateProcess([this]() { + // Given there is no mw_com_config.json in the default path + WithNoFileAtDefaultPath(); + + const auto configuration = runtime::RuntimeConfiguration{config_with_tire_pressure_port_other_}; + + // When loading an initial mw_com_config.json + Runtime::Initialize(configuration); + + // ... and another add-on configuration afterward + 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()->GetServiceTypes().size(), 2); + }); +} + +TEST_F(RuntimeInitializationTest, ConcurrentAddonConfigurationInitializationSucceeds) +{ + TestInSeparateProcess([this]() { + // Given there is no mw_com_config.json in the default path + WithNoFileAtDefaultPath(); + + // And 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()->GetServiceTypes().size(), 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() + 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( + { + WithNoFileAtDefaultPath(); + // 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); + auto& runtime = static_cast(Runtime::getInstance()); + // When loading the same configuration via InitializeRuntimeAddonConfiguration() + const auto add_on_configuration = runtime::RuntimeConfiguration{config_with_tire_pressure_port_}; + 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/impl/runtime_test.cpp b/score/mw/com/impl/runtime_test.cpp index 7ff57371b..a7ed62860 100644 --- a/score/mw/com/impl/runtime_test.cpp +++ b/score/mw/com/impl/runtime_test.cpp @@ -49,7 +49,7 @@ using testing::Return; TEST(RuntimeTest, CanRetrieveServiceDiscovery) { Configuration dummy_configuration{Configuration::ServiceTypeDeployments{}, - Configuration::ServiceInstanceDeployments{}, + ServiceInstancesContainer{Configuration::ServiceInstanceDeployments{}}, GlobalConfiguration{}, TracingConfiguration{}}; std::optional empty_filter_configuration{}; @@ -74,19 +74,21 @@ class RuntimeFixture : public ::testing::Test RuntimeFixture& WithAnEmptyConfiguration() noexcept { - configuration_ = std::make_unique(Configuration::ServiceTypeDeployments{}, - Configuration::ServiceInstanceDeployments{}, - GlobalConfiguration{}, - TracingConfiguration{}); + configuration_ = + std::make_unique(Configuration::ServiceTypeDeployments{}, + ServiceInstancesContainer{Configuration::ServiceInstanceDeployments{}}, + GlobalConfiguration{}, + TracingConfiguration{}); return *this; } RuntimeFixture& WithAConfigurationContaining(TracingConfiguration tracing_configuration) noexcept { - configuration_ = std::make_unique(Configuration::ServiceTypeDeployments{}, - Configuration::ServiceInstanceDeployments{}, - GlobalConfiguration{}, - std::move(tracing_configuration)); + configuration_ = + std::make_unique(Configuration::ServiceTypeDeployments{}, + ServiceInstancesContainer{Configuration::ServiceInstanceDeployments{}}, + GlobalConfiguration{}, + std::move(tracing_configuration)); return *this; } diff --git a/score/mw/com/impl/tracing/configuration/tracing_filter_config.cpp b/score/mw/com/impl/tracing/configuration/tracing_filter_config.cpp index 7fa98ea73..7af6da64c 100644 --- a/score/mw/com/impl/tracing/configuration/tracing_filter_config.cpp +++ b/score/mw/com/impl/tracing/configuration/tracing_filter_config.cpp @@ -207,10 +207,10 @@ std::size_t FindNumberOfTracingSlots( std::terminate(); } - const auto& service = configuration.GetServiceInstances(); + const auto& service_instance = configuration.GetServiceInstances().find(instance_specifier_result.value()); - const auto service_instance_it = service.find(instance_specifier_result.value()); - if (service_instance_it == service.end()) + // const auto service_instance_it = service.find(instance_specifier_result.value()); + if (!service_instance.has_value()) { score::mw::log::LogFatal() << "Lola: provided service instance with name:" << instance_specifier_result.value() @@ -219,7 +219,7 @@ std::size_t FindNumberOfTracingSlots( } const auto* lola_service_instance_deployment = - std::get_if(&service_instance_it->second.bindingInfo_); + std::get_if(&service_instance.value().get().bindingInfo_); if (lola_service_instance_deployment == nullptr) { score::mw::log::LogFatal("lola") diff --git a/score/mw/com/impl/tracing/configuration/tracing_filter_config_test.cpp b/score/mw/com/impl/tracing/configuration/tracing_filter_config_test.cpp index 07279134f..0e97b931a 100644 --- a/score/mw/com/impl/tracing/configuration/tracing_filter_config_test.cpp +++ b/score/mw/com/impl/tracing/configuration/tracing_filter_config_test.cpp @@ -632,7 +632,7 @@ class ConfigurationFixture : public ::testing::Test instance_deployments.emplace(port_name, service_instance_deployment); configuration_.emplace(std::move(type_deployments), - std::move(instance_deployments), + ServiceInstancesContainer{std::move(instance_deployments)}, GlobalConfiguration{}, TracingConfiguration{}); } @@ -681,7 +681,7 @@ class ConfigurationFixture : public ::testing::Test instance_deployments.emplace(port_name, service_instance_deployment); configuration_.emplace(std::move(type_deployments), - std::move(instance_deployments), + ServiceInstancesContainer{std::move(instance_deployments)}, GlobalConfiguration{}, TracingConfiguration{}); } diff --git a/score/mw/com/runtime.cpp b/score/mw/com/runtime.cpp index 0fe5bd2ff..07614dde2 100644 --- a/score/mw/com/runtime.cpp +++ b/score/mw/com/runtime.cpp @@ -63,6 +63,16 @@ void InitializeRuntime(const std::int32_t argc, score::StringLiteral argv[]) InitializeRuntime(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)); +} + void InitializeRuntime(const RuntimeConfiguration& runtime_configuration) { if (auto* const runtime_mock_holder = detail::RuntimeMockHolder::GetRuntimeMock()) diff --git a/score/mw/com/runtime.h b/score/mw/com/runtime.h index d4c5add50..724a61818 100644 --- a/score/mw/com/runtime.h +++ b/score/mw/com/runtime.h @@ -24,6 +24,7 @@ #include "score/mw/com/runtime_configuration.h" #include "score/mw/com/types.h" +#include "score/json/json_parser.h" #include "score/result/result.h" #include "score/string_manipulation/string_literal.h" @@ -83,6 +84,27 @@ score::Result ResolveInstanceIDs(co // NOLINTNEXTLINE(modernize-avoid-c-arrays):C-style array tolerated for command line arguments void InitializeRuntime(const std::int32_t argc, score::StringLiteral argv[]); +/** + * \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); + /** * \api * \brief Initializes mw::com subsystem with the given configuration.