diff --git a/api/envoy/extensions/filters/http/dynamic_modules/v3/dynamic_modules.proto b/api/envoy/extensions/filters/http/dynamic_modules/v3/dynamic_modules.proto index 6e74df4d06db1..acde0f3c1c852 100644 --- a/api/envoy/extensions/filters/http/dynamic_modules/v3/dynamic_modules.proto +++ b/api/envoy/extensions/filters/http/dynamic_modules/v3/dynamic_modules.proto @@ -58,6 +58,10 @@ message DynamicModuleFilter { // value: aGVsbG8= # echo -n "hello" | base64 // google.protobuf.Any filter_config = 3; + + // Allows configuring the stat prefix for all metrics emitted by the filter. By default, the stat prefix + // is equal to `dynamicmodulescustom`. + string stat_prefix = 4; } // Configuration of the HTTP per-route filter for dynamic modules. This filter allows loading shared object files diff --git a/source/extensions/filters/http/dynamic_modules/factory.cc b/source/extensions/filters/http/dynamic_modules/factory.cc index 695814ef07f79..aacfeca686d93 100644 --- a/source/extensions/filters/http/dynamic_modules/factory.cc +++ b/source/extensions/filters/http/dynamic_modules/factory.cc @@ -30,7 +30,7 @@ absl::StatusOr DynamicModuleConfigFactory::createFilterFa filter_config = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( proto_config.filter_name(), config, std::move(dynamic_module.value()), - dual_info.scope, context); + dual_info.scope, proto_config.stat_prefix(), context); if (!filter_config.ok()) { return absl::InvalidArgumentError("Failed to create filter config: " + diff --git a/source/extensions/filters/http/dynamic_modules/filter_config.cc b/source/extensions/filters/http/dynamic_modules/filter_config.cc index 9c7768843cbbf..00f1bafed686b 100644 --- a/source/extensions/filters/http/dynamic_modules/filter_config.cc +++ b/source/extensions/filters/http/dynamic_modules/filter_config.cc @@ -8,9 +8,10 @@ namespace HttpFilters { DynamicModuleHttpFilterConfig::DynamicModuleHttpFilterConfig( const absl::string_view filter_name, const absl::string_view filter_config, Extensions::DynamicModules::DynamicModulePtr dynamic_module, Stats::Scope& stats_scope, - Server::Configuration::ServerFactoryContext& context) + const std::string stat_prefix, Server::Configuration::ServerFactoryContext& context) : cluster_manager_(context.clusterManager()), - stats_scope_(stats_scope.createScope(std::string(CustomStatNamespace) + ".")), + stats_scope_(stats_scope.createScope( + (stat_prefix.length() > 0 ? stat_prefix : std::string(CustomStatNamespace)) + ".")), stat_name_pool_(stats_scope_->symbolTable()), filter_name_(filter_name), filter_config_(filter_config), dynamic_module_(std::move(dynamic_module)) {}; @@ -54,7 +55,7 @@ newDynamicModuleHttpPerRouteConfig(const absl::string_view per_route_config_name absl::StatusOr newDynamicModuleHttpFilterConfig( const absl::string_view filter_name, const absl::string_view filter_config, Extensions::DynamicModules::DynamicModulePtr dynamic_module, Stats::Scope& stats_scope, - Server::Configuration::ServerFactoryContext& context) { + const std::string stat_prefix, Server::Configuration::ServerFactoryContext& context) { auto constructor = dynamic_module->getFunctionPointer( "envoy_dynamic_module_on_http_filter_config_new"); @@ -110,7 +111,7 @@ absl::StatusOr newDynamicModuleHttpFilte RETURN_IF_NOT_OK_REF(on_scheduled.status()); auto config = std::make_shared( - filter_name, filter_config, std::move(dynamic_module), stats_scope, context); + filter_name, filter_config, std::move(dynamic_module), stats_scope, stat_prefix, context); const void* filter_config_envoy_ptr = (*constructor.value())(static_cast(config.get()), filter_name.data(), diff --git a/source/extensions/filters/http/dynamic_modules/filter_config.h b/source/extensions/filters/http/dynamic_modules/filter_config.h index ad99e74acf8ba..cc5070b790475 100644 --- a/source/extensions/filters/http/dynamic_modules/filter_config.h +++ b/source/extensions/filters/http/dynamic_modules/filter_config.h @@ -54,6 +54,7 @@ class DynamicModuleHttpFilterConfig { DynamicModuleHttpFilterConfig(const absl::string_view filter_name, const absl::string_view filter_config, DynamicModulePtr dynamic_module, Stats::Scope& stats_scope, + const std::string stat_prefix, Server::Configuration::ServerFactoryContext& context); ~DynamicModuleHttpFilterConfig(); @@ -309,13 +310,14 @@ newDynamicModuleHttpPerRouteConfig(const absl::string_view per_route_config_name * @param filter_name the name of the filter. * @param filter_config the configuration for the module. * @param dynamic_module the dynamic module to use. + * @param stat_prefix the stat prefix for the filter. * @param context the server factory context. * @return a shared pointer to the new config object or an error if the module could not be loaded. */ absl::StatusOr newDynamicModuleHttpFilterConfig( const absl::string_view filter_name, const absl::string_view filter_config, Extensions::DynamicModules::DynamicModulePtr dynamic_module, Stats::Scope& stats_scope, - Server::Configuration::ServerFactoryContext& context); + const std::string stat_prefix, Server::Configuration::ServerFactoryContext& context); } // namespace HttpFilters } // namespace DynamicModules diff --git a/test/extensions/dynamic_modules/http/abi_impl_test.cc b/test/extensions/dynamic_modules/http/abi_impl_test.cc index 1a79eab75c491..90a8cc398cbd3 100644 --- a/test/extensions/dynamic_modules/http/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/http/abi_impl_test.cc @@ -1103,7 +1103,7 @@ TEST(ABIImpl, Stats) { Stats::TestUtil::TestScope stats_scope{"", stats_store}; NiceMock context; auto filter_config = std::make_shared( - "some_name", "some_config", nullptr, stats_scope, context); + "some_name", "some_config", nullptr, stats_scope, "", context); DynamicModuleHttpFilter filter{filter_config, stats_scope.symbolTable()}; const std::string counter_name{"some_counter"}; diff --git a/test/extensions/dynamic_modules/http/filter_test.cc b/test/extensions/dynamic_modules/http/filter_test.cc index 687e5d43ae465..028b88bb04ef3 100644 --- a/test/extensions/dynamic_modules/http/filter_test.cc +++ b/test/extensions/dynamic_modules/http/filter_test.cc @@ -31,7 +31,7 @@ TEST_P(DynamicModuleTestLanguages, Nop) { auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( filter_name, filter_config, std::move(dynamic_module.value()), - *stats_store.createScope(""), context); + *stats_store.createScope(""), "", context); EXPECT_TRUE(filter_config_or_status.ok()); auto filter = std::make_shared(filter_config_or_status.value(), @@ -71,7 +71,7 @@ TEST(DynamicModulesTest, ConfigInitializationFailure) { Stats::IsolatedStoreImpl stats_store; auto filter_config_or_status = newDynamicModuleHttpFilterConfig("config_init_failure", "", std::move(dynamic_module.value()), - *stats_store.createScope(""), context); + *stats_store.createScope(""), "", context); EXPECT_FALSE(filter_config_or_status.ok()); EXPECT_THAT(filter_config_or_status.status().message(), testing::HasSubstr("Failed to initialize dynamic module")); @@ -92,7 +92,7 @@ TEST(DynamicModulesTest, StatsCallbacks) { Stats::TestUtil::TestScope stats_scope{"", stats_store}; auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( - filter_name, filter_config, std::move(dynamic_module.value()), stats_scope, context); + filter_name, filter_config, std::move(dynamic_module.value()), stats_scope, "", context); EXPECT_TRUE(filter_config_or_status.ok()); auto filter = std::make_shared(filter_config_or_status.value(), @@ -197,6 +197,34 @@ TEST(DynamicModulesTest, StatsCallbacks) { filter->onDestroy(); } +TEST(DynamicModulesTest, StatsCallbacksWithPrefix) { + const std::string filter_name = "stats_callbacks"; + const std::string filter_config = ""; + // TODO: Add non-Rust test program once we have non-Rust SDK. + auto dynamic_module = newDynamicModule(testSharedObjectPath("http", "rust"), false); + if (!dynamic_module.ok()) { + ENVOY_LOG_MISC(debug, "Failed to load dynamic module: {}", dynamic_module.status().message()); + } + EXPECT_TRUE(dynamic_module.ok()); + + NiceMock context; + Stats::TestUtil::TestStore stats_store; + Stats::TestUtil::TestScope stats_scope{"", stats_store}; + auto filter_config_or_status = + Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( + filter_name, filter_config, std::move(dynamic_module.value()), stats_scope, "fooprefix", + context); + EXPECT_TRUE(filter_config_or_status.ok()); + + auto filter = std::make_shared(filter_config_or_status.value(), + stats_scope.symbolTable()); + filter->initializeInModuleFilter(); + + Stats::CounterOptConstRef counter = stats_store.findCounterByString("fooprefix.streams_total"); + EXPECT_TRUE(counter.has_value()); + EXPECT_EQ(counter->get().value(), 1); +} + TEST(DynamicModulesTest, HeaderCallbacks) { const std::string filter_name = "header_callbacks"; const std::string filter_config = ""; @@ -212,7 +240,7 @@ TEST(DynamicModulesTest, HeaderCallbacks) { auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( filter_name, filter_config, std::move(dynamic_module.value()), - *stats_store.createScope(""), context); + *stats_store.createScope(""), "", context); EXPECT_TRUE(filter_config_or_status.ok()); auto filter = std::make_shared(filter_config_or_status.value(), @@ -274,7 +302,7 @@ TEST(DynamicModulesTest, DynamicMetadataCallbacks) { auto stats_scope = stats_store.createScope(""); auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( - filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, context); + filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, "", context); EXPECT_TRUE(filter_config_or_status.ok()); auto filter = std::make_shared(filter_config_or_status.value(), @@ -357,7 +385,7 @@ TEST(DynamicModulesTest, FilterStateCallbacks) { auto stats_scope = stats_store.createScope(""); auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( - filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, context); + filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, "", context); EXPECT_TRUE(filter_config_or_status.ok()); auto filter = std::make_shared(filter_config_or_status.value(), @@ -434,7 +462,7 @@ TEST(DynamicModulesTest, BodyCallbacks) { auto stats_scope = stats_store.createScope(""); auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( - filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, context); + filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, "", context); EXPECT_TRUE(filter_config_or_status.ok()); auto filter = std::make_shared(filter_config_or_status.value(), @@ -511,7 +539,7 @@ TEST(DynamicModulesTest, HttpFilterHttpCallout_non_existing_cluster) { .WillOnce(testing::Return(nullptr)); auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( - filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, context); + filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, "", context); EXPECT_TRUE(filter_config_or_status.ok()); Http::MockStreamDecoderFilterCallbacks callbacks; @@ -548,7 +576,7 @@ TEST(DynamicModulesTest, HttpFilterHttpCallout_immediate_failing_cluster) { const std::string filter_config = "immediate_failing_cluster"; auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( - filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, context); + filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, "", context); EXPECT_TRUE(filter_config_or_status.ok()); std::shared_ptr cluster = @@ -601,7 +629,7 @@ TEST(DynamicModulesTest, HttpFilterHttpCallout_success) { const std::string filter_config = "success_cluster"; auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( - filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, context); + filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, "", context); EXPECT_TRUE(filter_config_or_status.ok()); std::shared_ptr cluster = @@ -670,7 +698,7 @@ TEST(DynamicModulesTest, HttpFilterHttpCallout_resetting) { const std::string filter_config = "resetting_cluster"; auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( - filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, context); + filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, "", context); EXPECT_TRUE(filter_config_or_status.ok()); std::shared_ptr cluster = @@ -723,7 +751,7 @@ TEST(DynamicModulesTest, HttpFilterPerFilterConfigLifetimes) { const std::string filter_config = "listener config"; auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( - filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, context); + filter_name, filter_config, std::move(dynamic_module.value()), *stats_scope, "", context); EXPECT_TRUE(filter_config_or_status.ok()); auto dynamic_module_for_route = diff --git a/test/extensions/dynamic_modules/http/integration_test.cc b/test/extensions/dynamic_modules/http/integration_test.cc index b8836e7835ae2..50aaf08da2c83 100644 --- a/test/extensions/dynamic_modules/http/integration_test.cc +++ b/test/extensions/dynamic_modules/http/integration_test.cc @@ -14,7 +14,7 @@ class DynamicModulesIntegrationTest : public testing::TestWithParamencodeHeaders(response_headers, true); ASSERT_TRUE(response->waitForEndStream()); EXPECT_TRUE(response->complete()); } } +TEST_P(DynamicModulesIntegrationTest, StatsCallbacksWithPrefix) { + initializeFilter("stats_callbacks", "header_to_count,header_to_set", "", + "type.googleapis.com/google.protobuf.StringValue", false, "fooprefix"); + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + + Http::TestRequestHeaderMapImpl request_headers = default_request_headers_; + request_headers.addCopy(Http::LowerCaseString("header_to_count"), "13"); + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + waitForNextUpstreamRequest(); + test_server_->waitUntilHistogramHasSamples("fooprefix.requests_header_values"); + + EXPECT_EQ(test_server_->counter("fooprefix.requests_total")->value(), 1); + + Http::TestResponseHeaderMapImpl response_headers = default_response_headers_; + upstream_request_->encodeHeaders(response_headers, true); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); +} + TEST_P(DynamicModulesIntegrationTest, InjectBody) { initializeFilter("inject_body"); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http"))));