diff --git a/api/envoy/extensions/filters/http/fault/v3/fault.proto b/api/envoy/extensions/filters/http/fault/v3/fault.proto index d28ed28b1110..fb3c51cca9d0 100644 --- a/api/envoy/extensions/filters/http/fault/v3/fault.proto +++ b/api/envoy/extensions/filters/http/fault/v3/fault.proto @@ -54,7 +54,7 @@ message FaultAbort { type.v3.FractionalPercent percentage = 3; } -// [#next-free-field: 15] +// [#next-free-field: 16] message HTTPFault { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.fault.v2.HTTPFault"; @@ -141,4 +141,10 @@ message HTTPFault { // The runtime key to override the :ref:`default ` // runtime. The default is: fault.http.abort.grpc_status string abort_grpc_status_runtime = 14; + + // To control whether stats storage is allocated dynamically for each downstream server. + // If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + // If set to false, dynamic stats storage will be allocated for the downstream cluster name. + // Default value is false. + bool disable_downstream_cluster_stats = 15; } diff --git a/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto b/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto index 7dd4f48aa476..515500726845 100644 --- a/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto +++ b/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto @@ -54,7 +54,7 @@ message FaultAbort { type.v3.FractionalPercent percentage = 3; } -// [#next-free-field: 15] +// [#next-free-field: 16] message HTTPFault { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.fault.v3.HTTPFault"; @@ -141,4 +141,10 @@ message HTTPFault { // The runtime key to override the :ref:`default ` // runtime. The default is: fault.http.abort.grpc_status string abort_grpc_status_runtime = 14; + + // To control whether stats storage is allocated dynamically for each downstream server. + // If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + // If set to false, dynamic stats storage will be allocated for the downstream cluster name. + // Default value is false. + bool disable_downstream_cluster_stats = 15; } diff --git a/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto b/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto index d28ed28b1110..fb3c51cca9d0 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto @@ -54,7 +54,7 @@ message FaultAbort { type.v3.FractionalPercent percentage = 3; } -// [#next-free-field: 15] +// [#next-free-field: 16] message HTTPFault { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.fault.v2.HTTPFault"; @@ -141,4 +141,10 @@ message HTTPFault { // The runtime key to override the :ref:`default ` // runtime. The default is: fault.http.abort.grpc_status string abort_grpc_status_runtime = 14; + + // To control whether stats storage is allocated dynamically for each downstream server. + // If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + // If set to false, dynamic stats storage will be allocated for the downstream cluster name. + // Default value is false. + bool disable_downstream_cluster_stats = 15; } diff --git a/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto b/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto index 7dd4f48aa476..515500726845 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto @@ -54,7 +54,7 @@ message FaultAbort { type.v3.FractionalPercent percentage = 3; } -// [#next-free-field: 15] +// [#next-free-field: 16] message HTTPFault { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.fault.v3.HTTPFault"; @@ -141,4 +141,10 @@ message HTTPFault { // The runtime key to override the :ref:`default ` // runtime. The default is: fault.http.abort.grpc_status string abort_grpc_status_runtime = 14; + + // To control whether stats storage is allocated dynamically for each downstream server. + // If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + // If set to false, dynamic stats storage will be allocated for the downstream cluster name. + // Default value is false. + bool disable_downstream_cluster_stats = 15; } diff --git a/source/extensions/filters/http/fault/fault_filter.cc b/source/extensions/filters/http/fault/fault_filter.cc index ca1ff7f42a9e..de302a221698 100644 --- a/source/extensions/filters/http/fault/fault_filter.cc +++ b/source/extensions/filters/http/fault/fault_filter.cc @@ -51,7 +51,8 @@ FaultSettings::FaultSettings(const envoy::extensions::filters::http::fault::v3:: fault, max_active_faults_runtime, RuntimeKeys::get().MaxActiveFaultsKey)), response_rate_limit_percent_runtime_( PROTOBUF_GET_STRING_OR_DEFAULT(fault, response_rate_limit_percent_runtime, - RuntimeKeys::get().ResponseRateLimitPercentKey)) { + RuntimeKeys::get().ResponseRateLimitPercentKey)), + disable_downstream_cluster_stats_(fault.disable_downstream_cluster_stats()) { if (fault.has_abort()) { request_abort_config_ = std::make_unique(fault.abort()); @@ -89,8 +90,10 @@ FaultFilterConfig::FaultFilterConfig( stats_prefix_(stat_name_set_->add(absl::StrCat(stats_prefix, "fault"))) {} void FaultFilterConfig::incCounter(Stats::StatName downstream_cluster, Stats::StatName stat_name) { - Stats::Utility::counterFromStatNames(scope_, {stats_prefix_, downstream_cluster, stat_name}) - .inc(); + if (!settings_.disableDownstreamClusterStats()) { + Stats::Utility::counterFromStatNames(scope_, {stats_prefix_, downstream_cluster, stat_name}) + .inc(); + } } FaultFilter::FaultFilter(FaultFilterConfigSharedPtr config) : config_(config) {} @@ -134,7 +137,7 @@ Http::FilterHeadersStatus FaultFilter::decodeHeaders(Http::RequestHeaderMap& hea if (headers.EnvoyDownstreamServiceCluster()) { downstream_cluster_ = std::string(headers.getEnvoyDownstreamServiceClusterValue()); - if (!downstream_cluster_.empty()) { + if (!downstream_cluster_.empty() && !fault_settings_->disableDownstreamClusterStats()) { downstream_cluster_storage_ = std::make_unique( downstream_cluster_, config_->scope().symbolTable()); } @@ -369,7 +372,7 @@ FaultFilter::abortGrpcStatus(const Http::RequestHeaderMap& request_headers) { void FaultFilter::recordDelaysInjectedStats() { // Downstream specific stats. - if (!downstream_cluster_.empty()) { + if (!downstream_cluster_.empty() && !fault_settings_->disableDownstreamClusterStats()) { config_->incDelays(downstream_cluster_storage_->statName()); } @@ -378,7 +381,7 @@ void FaultFilter::recordDelaysInjectedStats() { void FaultFilter::recordAbortsInjectedStats() { // Downstream specific stats. - if (!downstream_cluster_.empty()) { + if (!downstream_cluster_.empty() && !fault_settings_->disableDownstreamClusterStats()) { config_->incAborts(downstream_cluster_storage_->statName()); } diff --git a/source/extensions/filters/http/fault/fault_filter.h b/source/extensions/filters/http/fault/fault_filter.h index 5a75f1244db7..fefea8bd06f5 100644 --- a/source/extensions/filters/http/fault/fault_filter.h +++ b/source/extensions/filters/http/fault/fault_filter.h @@ -74,6 +74,7 @@ class FaultSettings : public Router::RouteSpecificFilterConfig { const std::string& responseRateLimitPercentRuntime() const { return response_rate_limit_percent_runtime_; } + bool disableDownstreamClusterStats() const { return disable_downstream_cluster_stats_; } private: class RuntimeKeyValues { @@ -96,6 +97,7 @@ class FaultSettings : public Router::RouteSpecificFilterConfig { const std::vector fault_filter_headers_; absl::flat_hash_set downstream_nodes_{}; // Inject failures for specific downstream absl::optional max_active_faults_; + Filters::Common::Fault::FaultRateLimitConfigPtr response_rate_limit_; const std::string delay_percent_runtime_; const std::string abort_percent_runtime_; @@ -104,6 +106,7 @@ class FaultSettings : public Router::RouteSpecificFilterConfig { const std::string abort_grpc_status_runtime_; const std::string max_active_faults_runtime_; const std::string response_rate_limit_percent_runtime_; + const bool disable_downstream_cluster_stats_; }; /** diff --git a/test/extensions/filters/http/fault/fault_filter_integration_test.cc b/test/extensions/filters/http/fault/fault_filter_integration_test.cc index c00bf3ee55a0..f23633aa308a 100644 --- a/test/extensions/filters/http/fault/fault_filter_integration_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_integration_test.cc @@ -48,6 +48,26 @@ name: fault numerator: 100 )EOF"; + const std::string disable_stats_fault_config_ = + R"EOF( +name: fault +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault + abort: + header_abort: {} + percentage: + numerator: 100 + delay: + header_delay: {} + percentage: + numerator: 100 + response_rate_limit: + header_limit: {} + percentage: + numerator: 100 + disable_downstream_cluster_stats: true +)EOF"; + const std::string abort_grpc_fault_config_ = R"EOF( name: fault @@ -173,6 +193,56 @@ TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultAbortConfig) { EXPECT_EQ(0UL, test_server_->gauge("http.config_test.fault.active_faults")->value()); } +// Request abort controlled via header configuration and enable downstream server stats. +TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultAbortConfigEnableDownstreamServerStats) { + initializeFilter(header_fault_config_); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-envoy-fault-abort-request", "429"}, + {"x-envoy-downstream-service-cluster", "superman"}}); + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(response->complete()); + EXPECT_THAT(response->headers(), Envoy::Http::HttpStatusIs("429")); + + EXPECT_EQ(1UL, test_server_->counter("http.config_test.fault.aborts_injected")->value()); + EXPECT_EQ(0UL, test_server_->counter("http.config_test.fault.delays_injected")->value()); + EXPECT_EQ(0UL, test_server_->counter("http.config_test.fault.response_rl_injected")->value()); + EXPECT_EQ(0UL, test_server_->gauge("http.config_test.fault.active_faults")->value()); + EXPECT_EQ(1UL, test_server_->counter("http.config_test.fault.superman.aborts_injected")->value()); + EXPECT_EQ(nullptr, test_server_->counter("http.config_test.fault.superman.delays_injected")); +} + +// Request abort controlled via header configuration and disable downstream server stats. +TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultAbortConfigDisableDownstreamServerStats) { + initializeFilter(disable_stats_fault_config_); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-envoy-fault-abort-request", "429"}, + {"x-envoy-downstream-service-cluster", "superman"}}); + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(response->complete()); + EXPECT_THAT(response->headers(), Envoy::Http::HttpStatusIs("429")); + + EXPECT_EQ(1UL, test_server_->counter("http.config_test.fault.aborts_injected")->value()); + EXPECT_EQ(0UL, test_server_->counter("http.config_test.fault.delays_injected")->value()); + EXPECT_EQ(0UL, test_server_->counter("http.config_test.fault.response_rl_injected")->value()); + EXPECT_EQ(0UL, test_server_->gauge("http.config_test.fault.active_faults")->value()); + EXPECT_EQ(nullptr, test_server_->counter("http.config_test.fault.superman.aborts_injected")); + EXPECT_EQ(nullptr, test_server_->counter("http.config_test.fault.superman.delays_injected")); +} + // Request faults controlled via header configuration. TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultsConfig0PercentageHeaders) { initializeFilter(header_fault_config_); diff --git a/test/extensions/filters/http/fault/fault_filter_test.cc b/test/extensions/filters/http/fault/fault_filter_test.cc index c40a80171ed2..07d16bd6d41a 100644 --- a/test/extensions/filters/http/fault/fault_filter_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_test.cc @@ -66,6 +66,15 @@ class FaultFilterTest : public testing::Test { fixed_delay: 5s )EOF"; + const std::string fixed_delay_only_disable_stats_yaml = R"EOF( + delay: + percentage: + numerator: 100 + denominator: HUNDRED + fixed_delay: 5s + disable_downstream_cluster_stats: true + )EOF"; + const std::string abort_only_yaml = R"EOF( abort: percentage: @@ -717,6 +726,55 @@ TEST_F(FaultFilterTest, DelayForDownstreamCluster) { EXPECT_EQ(0UL, stats_.counter("prefix.fault.cluster.aborts_injected").value()); } +TEST_F(FaultFilterTest, DelayForDownstreamClusterDisableTracing) { + setUpTest(fixed_delay_only_disable_stats_yaml); + + EXPECT_CALL(runtime_.snapshot_, + getInteger("fault.http.max_active_faults", std::numeric_limits::max())) + .WillOnce(Return(std::numeric_limits::max())); + + request_headers_.addCopy("x-envoy-downstream-service-cluster", "cluster"); + + // Delay related calls. + EXPECT_CALL( + runtime_.snapshot_, + featureEnabled("fault.http.cluster.delay.fixed_delay_percent", + testing::Matcher(Percent(100)))) + .WillOnce(Return(true)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.delay.fixed_duration_ms", 5000)) + .WillOnce(Return(125UL)); + EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.cluster.delay.fixed_duration_ms", 125UL)) + .WillOnce(Return(500UL)); + expectDelayTimer(500UL); + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(StreamInfo::ResponseFlag::DelayInjected)); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_, false)); + + // Delay only case, no aborts. + EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.cluster.abort.http_status", _)).Times(0); + EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.abort.http_status", _)).Times(0); + EXPECT_CALL(decoder_filter_callbacks_, encodeHeaders_(_, _)).Times(0); + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(StreamInfo::ResponseFlag::FaultInjected)) + .Times(0); + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, false)); + + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, popTrackedObject(_)); + timer_->invokeCallback(); + + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(1UL, config_->stats().delays_injected_.value()); + EXPECT_EQ(0UL, config_->stats().aborts_injected_.value()); + EXPECT_EQ(0UL, stats_.counter("prefix.fault.cluster.delays_injected").value()); + EXPECT_EQ(0UL, stats_.counter("prefix.fault.cluster.aborts_injected").value()); +} + TEST_F(FaultFilterTest, FixedDelayAndAbortDownstream) { setUpTest(fixed_delay_and_abort_yaml);